@@ -435,7 +435,7 @@ def slit_pa(ext):
435435 else :
436436 log .warning (f"{ ad .filename } :{ ext .id } Cross-correlation failed" )
437437
438- if debug_plots :
438+ if debug_plots : # pragma: no cover
439439 fig , ax = plt .subplots ()
440440 print (f"Comparing { ad .filename } to reference { refad .filename } "
441441 f"(expected peak at { expected_peak - corr .size // 2 } )" )
@@ -1545,7 +1545,7 @@ def determineDistortion(self, adinputs=None, **params):
15451545 # TODO: Some logging about quality of fit
15461546 # print(np.min(diff), np.max(diff), np.std(diff))
15471547
1548- if debug :
1548+ if debug : # pragma: no cover
15491549 self .viewer .color = "red"
15501550 spatial_coords = np .linspace (ref_coords [dispaxis ].min (), ref_coords [dispaxis ].max (),
15511551 ext .shape [1 - dispaxis ] // (step * 10 ))
@@ -1924,7 +1924,7 @@ def find_slits(mdf, ystep=50):
19241924 log .warning (f"Did not find expected number of { slit_name } s "
19251925 f"(found { nfound } , expected { len (slit_lengths )} )." )
19261926
1927- if debug_plots :
1927+ if debug_plots : # pragma: no cover
19281928 for pos in edges_1 :
19291929 if pos :
19301930 plt .axvline (pos , color = 'Blue' , alpha = 0.5 ,
@@ -3151,7 +3151,7 @@ def flagCosmicRays(self, adinputs=None, **params):
31513151 del crmask , skyfit_input
31523152 gc .collect ()
31533153
3154- if debug :
3154+ if debug : # pragma: no cover
31553155 fig , axes = plt .subplots (5 , 3 , sharex = True , sharey = True ,
31563156 tight_layout = True )
31573157 for i , ext in enumerate (ad_tiled ):
@@ -3387,10 +3387,18 @@ def fluxCalibrate(self, adinputs=None, **params):
33873387 # Reconstruct the spline and evaluate it at every wavelength
33883388 sens_factor = sensfunc (waves .to (std_wave_unit ).value ) * std_flux_unit
33893389 try : # conversion from magnitude/logarithmic units
3390- sens_factor = sens_factor .physical
3390+ # See comment below
3391+ with warnings .catch_warnings (category = RuntimeWarning ,
3392+ action = "ignore" ):
3393+ sens_factor = sens_factor .physical
33913394 except AttributeError :
33923395 pass
33933396
3397+ # This avoids extrapolative blow-ups when flux-calibrating XD
3398+ # data that covers the full wavelength range but the standard
3399+ # only covers a small part.
3400+ sens_factor [(ext .mask & DQ .no_data ) > 0 ] = 0
3401+
33943402 # Apply airmass correction. If none is needed/possible, we
33953403 # don't need to try to do this
33963404 if delta_airmass :
@@ -3942,6 +3950,8 @@ def resampleToCommonFrame(self, adinputs=None, **params):
39423950 r + ref_pixels_dict [j ][0 ]).T .astype (int )
39433951 for ad , r in zip (adinputs ,
39443952 ref_pixels_dict [j ])]
3953+ else :
3954+ dispaxis = 0
39453955
39463956 # Gather information from all the spectra (Chebyshev1D model,
39473957 # w1, w2, dw, npix), and compute the final bounds (w1out, w2out)
@@ -3963,9 +3973,13 @@ def resampleToCommonFrame(self, adinputs=None, **params):
39633973 adinfo .append (model_info )
39643974 w1_arr [i , iext ] = model_info ['w1' ]
39653975 w2_arr [i , iext ] = model_info ['w2' ]
3966-
39673976 info .append (adinfo )
39683977
3978+ # Are we combining multiple spectra with different wavelength settings
3979+ # into a single spectrum? This is important for later.
3980+ combining_multiple_wavelengths = (single_spectral and
3981+ len (set (w1_arr .ravel ())) > 1 )
3982+
39693983 # Compute the output wavelength range for each extension. We can
39703984 # calculate the overall output range if we're combining to a single
39713985 # extension later
@@ -3994,15 +4008,16 @@ def resampleToCommonFrame(self, adinputs=None, **params):
39944008 # parameters as the 4th is then calculable. First, we copy the
39954009 # start and end wavelengths if those aren't specified. If neither
39964010 # dw nor npix are specified, the behaviour depends on whether we
3997- # are resampling to a single wavelength scale: if so, then we want
3998- # to preserve the dispersion to avoid undersampling but, if not,
3999- # then we want to preserve the number of pixels per extension.
4011+ # are resampling multiple spectra to a single wavelength scale:
4012+ # if so, then we want to preserve the dispersion to avoid
4013+ # undersampling but, if not, then we want to preserve the number
4014+ # of pixels per extension.
40004015 while nparams < 3 :
40014016 if w1 is None :
40024017 w1 = wave_min
40034018 elif w2 is None :
40044019 w2 = wave_max
4005- elif single_spectral and dw is None :
4020+ elif combining_multiple_wavelengths and dw is None :
40064021 w1 = np .full_like (w1 , np .nanmin (w1 ))
40074022 w2 = np .full_like (w2 , np .nanmax (w2 ))
40084023 if output_spectral == "linear" :
@@ -4011,7 +4026,7 @@ def resampleToCommonFrame(self, adinputs=None, **params):
40114026 else :
40124027 # dw has been calculated assuming the spectrum is
40134028 # linear, so we repeat that assumption
4014- dw = np .array ([extinfo ['dw' ] / extinfo ['w2' ] - 1
4029+ dw = np .array ([extinfo ['dw' ] / extinfo ['w2' ]
40154030 for adinfo in info for extinfo in adinfo ])
40164031 dw = np .full_like (w1 , dw .min ())
40174032 elif npix is None :
@@ -4025,12 +4040,18 @@ def resampleToCommonFrame(self, adinputs=None, **params):
40254040 npix = np .ceil ((w2 - w1 ) / dw ).astype (int ) + 1
40264041 w2 = w1 + (npix - 1 ) * dw
40274042 else : # loglinear
4028- npix = np .ceil (np .log (w2 / w1 ) / np .log (1 + dw ) - 1 )
4043+ npix = np .ceil (np .log (w2 / w1 ) / np .log (1 + dw ) - 1 ). astype ( int ) + 1
40294044 w2 = w1 * (1 + dw ) ** (npix - 1 )
40304045 elif w1 is None :
4031- w1 = w2 - (npix - 1 ) * dw
4046+ if output_spectral == "linear" :
4047+ w1 = w2 - (npix - 1 ) * dw
4048+ else : # loglinear
4049+ w1 = w2 / (1 + dw ) ** (npix - 1 )
40324050 elif w2 is None :
4033- w2 = w1 + (npix - 1 ) * dw
4051+ if output_spectral == "linear" :
4052+ w2 = w1 + (npix - 1 ) * dw
4053+ else : # loglinear
4054+ w2 = w1 * (1 + dw ) ** (npix - 1 )
40344055 elif output_spectral == "linear" : # dw is None
40354056 dw = (w2 - w1 ) / (npix - 1 )
40364057 else : # dw is None and we're loglinearizing
@@ -4084,7 +4105,6 @@ def resampleToCommonFrame(self, adinputs=None, **params):
40844105 actual_limits = new_wave_model ([0 , this_npix - 1 ])
40854106 w1 [iext ] = actual_limits .min ()
40864107 w2 [iext ] = actual_limits .max ()
4087- yy = new_wave_model ([this_npix - 3 ,this_npix - 2 ,this_npix - 1 ])
40884108
40894109 # Calculation for all extensions
40904110 dw = (w2 - w1 ) / (npix - 1 )
@@ -4164,6 +4184,8 @@ def resampleToCommonFrame(self, adinputs=None, **params):
41644184 if i == 0 and not new_wave_scale :
41654185 log .fullinfo (f"{ ad .filename } : No interpolation" )
41664186 msg = "Resampling"
4187+ if this_conserve :
4188+ msg += " (with flux conservation)"
41674189 if new_wave_scale :
41684190 msg += f" and { output_spectral } izing"
41694191 dwstr = (f"{ dw [iext ]:.6f} " if output_spectral == "loglinear"
@@ -5220,7 +5242,8 @@ def _get_spectrophotometry(self, filename, in_vacuo=False):
52205242 if isinstance (unit , u .UnrecognizedUnit ):
52215243 # Try chopping off the trailing 's'
52225244 try :
5223- unit = u .Unit (re .sub (r's$' , '' , col .unit .name .lower ()))
5245+ unit = u .Unit (re .sub (r's$' , '' ,
5246+ col .unit .name .lower ()))
52245247 except :
52255248 unit = None
52265249 if unit is None :
@@ -5232,7 +5255,8 @@ def _get_spectrophotometry(self, filename, in_vacuo=False):
52325255 else :
52335256 if orig_colname == 'FNU' :
52345257 unit = u .Unit ("erg cm-2 s-1 Hz-1" )
5235- elif orig_colname in ('FLAM' , 'FLUX' ) or np .median (col .data ) < 1 :
5258+ elif (orig_colname in ('FLAM' , 'FLUX' ) or
5259+ np .median (col .data ) < 1 ):
52365260 unit = u .Unit ("erg cm-2 s-1 AA-1" )
52375261 else :
52385262 unit = u .mag
@@ -5241,15 +5265,21 @@ def _get_spectrophotometry(self, filename, in_vacuo=False):
52415265 # We've created a column called "MAGNITUDE" but it might be a flux
52425266 if col .name == 'MAGNITUDE' :
52435267 try :
5244- unit .to (u .W / u .m ** 3 , equivalencies = u .spectral_density (1. * u .m ))
5268+ unit .to (u .W / u .m ** 3 ,
5269+ equivalencies = u .spectral_density (1. * u .m ))
52455270 except :
52465271 pass
52475272 else :
52485273 col .name = 'FLUX'
52495274
5250- wavecol = spec_table ["WAVELENGTH" ].quantity
52515275 if in_vacuo is None :
5252- in_vacuo = min (wavecol ) < 300 * u .nm
5276+ in_vacuo = min (spec_table ["WAVELENGTH" ].quantity ) < 300 * u .nm
5277+
5278+ # The default (and best) specutils vacuum/air conversion has a
5279+ # singularity in the FUV, so we cut the wavelength scale.
5280+ # See https://github.com/astropy/specutils/issues/1162
5281+ spec_table = spec_table [spec_table ["WAVELENGTH" ] >= 300 * u .nm ]
5282+ wavecol = spec_table ["WAVELENGTH" ].quantity
52535283
52545284 if in_vacuo :
52555285 spec_table ["WAVELENGTH_VACUUM" ] = spec_table ["WAVELENGTH" ]
0 commit comments