1010from jwst .tests .helpers import LogWatcher
1111from jwst .white_light .white_light import white_light
1212
13+ TIME_KEYS = [
14+ "MJD-BEG" ,
15+ "MJD-AVG" ,
16+ "MJD-END" ,
17+ "TDB-BEG" ,
18+ "TDB-MID" ,
19+ "TDB-END" ,
20+ ]
21+
1322
1423@pytest .fixture (scope = "module" )
1524def make_datamodel ():
@@ -129,19 +138,12 @@ def make_datamodel():
129138
130139 # set blank times for one integration in order 1 to test warning raise
131140 model .spec [0 ].spec_table ["INT_NUM" ][2 ] = 0
132- time_keys = [
133- "MJD-BEG" ,
134- "MJD-AVG" ,
135- "MJD-END" ,
136- "TDB-BEG" ,
137- "TDB-MID" ,
138- "TDB-END" ,
139- ]
140- for key in time_keys :
141+
142+ for key in TIME_KEYS :
141143 model .spec [0 ].spec_table [key ][2 ] = np .nan
142144
143145 # set one time to a different value for order 2 to test table integration
144- for key in time_keys :
146+ for key in TIME_KEYS :
145147 model .spec [1 ].spec_table [key ][2 ] += 0.01
146148
147149 return model
@@ -151,7 +153,7 @@ def test_white_light(make_datamodel, monkeypatch):
151153 """Test white light step"""
152154 data = make_datamodel
153155
154- watcher = LogWatcher ("There were 1 spectra in order 1 with no mid time (20" )
156+ watcher = LogWatcher ("1 spectra in order 1 with no mid time or duplicate mid time (20" )
155157 monkeypatch .setattr (logging .getLogger ("jwst.white_light.white_light" ), "warning" , watcher )
156158 result = white_light (data )
157159 watcher .assert_seen ()
@@ -190,7 +192,7 @@ def test_white_light(make_datamodel, monkeypatch):
190192
191193
192194def test_white_light_multi_detector (make_datamodel ):
193- """Test white light step"""
195+ """Test white light step on data with multiple detectors. """
194196 # Set the detectors in the two spec tables to different values
195197 data = make_datamodel .copy ()
196198 data .spec [0 ].detector = "NRS1"
@@ -237,3 +239,142 @@ def test_white_light_multi_detector(make_datamodel):
237239
238240 assert_allclose (result ["whitelight_flux_order_1_NRS1" ], expected_flux_order_1 , equal_nan = True )
239241 assert_allclose (result ["whitelight_flux_order_2_NRS2" ], expected_flux_order_2 , equal_nan = True )
242+
243+
244+ def test_white_light_duplicate_times (monkeypatch , make_datamodel ):
245+ """Test white light step on data with duplicate time stamps."""
246+ # Set all times to the same value for order 1
247+ data = make_datamodel .copy ()
248+ for key in TIME_KEYS :
249+ data .spec [0 ].spec_table [key ][:] = data .spec [0 ].spec_table [key ][0 ]
250+
251+ watcher = LogWatcher ("4 spectra in order 1 with no mid time or duplicate mid time" )
252+ monkeypatch .setattr (logging .getLogger ("jwst.white_light.white_light" ), "warning" , watcher )
253+ result = white_light (data )
254+ watcher .assert_seen ()
255+
256+ # Expected times match input for the order 2 spectrum
257+ n_spec = len (data .spec [1 ].spec_table )
258+ mid_times = data .spec [1 ].spec_table ["MJD-AVG" ]
259+ expected_tdb = data .spec [1 ].spec_table ["TDB-MID" ]
260+
261+ np .testing .assert_allclose (result ["MJD_UTC" ], mid_times )
262+ np .testing .assert_allclose (result ["BJD_TDB" ], expected_tdb , equal_nan = True )
263+ assert result ["whitelight_flux_order_1" ].shape == (len (mid_times ),)
264+ assert result ["whitelight_flux_order_2" ].shape == (len (mid_times ),)
265+
266+ # For order 1, duplicate timestamps so only the first flux is kept
267+ assert np .sum (np .isnan (result ["whitelight_flux_order_1" ])) == n_spec - 1
268+
269+ # check fluxes are summed appropriately
270+ expected_flux_per_spec = np .sum (np .arange (1 , 21 , dtype = np .float32 ))
271+ expected_flux = np .array (
272+ [
273+ expected_flux_per_spec ,
274+ ]
275+ * len (mid_times )
276+ )
277+ expected_flux_order_1 = expected_flux .copy ()
278+ expected_flux_order_1 [1 :] = np .nan
279+ expected_flux_order_2 = expected_flux .copy ()
280+
281+ assert_allclose (result ["whitelight_flux_order_1" ], expected_flux_order_1 , equal_nan = True )
282+ assert_allclose (result ["whitelight_flux_order_2" ], expected_flux_order_2 , equal_nan = True )
283+
284+
285+ @pytest .fixture
286+ def wavelengthrange ():
287+ """Mock the wavelengthrange info from reference files."""
288+ orders = [1 , 1 , 2 , 3 ]
289+ filters = ["CLEAR" , "F277W" , "CLEAR" , "CLEAR" ]
290+ wl_min = [0.93 , 2.41 , 0.64 , 0.64 ]
291+ wl_max = [2.82 , 2.82 , 0.83 , 0.95 ]
292+ return Table (
293+ data = [orders , filters , wl_min , wl_max ],
294+ names = ["order" , "filter" , "min_wave" , "max_wave" ],
295+ dtype = [int , str , float , float ],
296+ )
297+
298+
299+ def test_determine_wavelength_range (wavelengthrange ):
300+ """Test that the wavelength range is determined correctly."""
301+ # retrieve from reference file
302+ wl_min , wl_max = _determine_wavelength_range (1 , "F277W" , waverange_table = wavelengthrange )
303+ assert wl_min == 2.41
304+ assert wl_max == 2.82
305+
306+ # user-specified values override reference file values
307+ wl_min , wl_max = _determine_wavelength_range (
308+ 1 , "F277W" , waverange_table = wavelengthrange , min_wave = 2.0 , max_wave = 3.0
309+ )
310+ assert wl_min == 2.0
311+ assert wl_max == 3.0
312+
313+ # use default values when no reference file is provided
314+ wl_min , wl_max = _determine_wavelength_range (4 , "CLEAR" )
315+ assert wl_min == - 1.0
316+ assert wl_max == 1.0e10
317+
318+
319+ def test_determine_wavelength_range_no_match (wavelengthrange ):
320+ """Test that an error is raised if no match is found."""
321+ with pytest .raises (
322+ ValueError , match = "No reference wavelength range found for order 4 and filter CLEAR"
323+ ):
324+ _determine_wavelength_range (4 , "CLEAR" , waverange_table = wavelengthrange )
325+
326+
327+ def test_determine_wavelength_range_multiple_matches (wavelengthrange ):
328+ """Test that an error is raised if more than one match is found."""
329+ wavelengthrange ["order" ][2 ] = 1
330+ with pytest .raises (
331+ ValueError , match = "Multiple reference wavelength ranges found for order 1 and filter CLEAR"
332+ ):
333+ _determine_wavelength_range (1 , "CLEAR" , waverange_table = wavelengthrange )
334+
335+
336+ def test_get_reference_wavelength_range (make_datamodel ):
337+ """Test reading of wavelength range reference file."""
338+ wr = WhiteLightStep ()._get_reference_wavelength_range (make_datamodel )
339+ assert isinstance (wr , Table )
340+ assert len (wr ) > 0
341+ assert "order" in wr .columns
342+ assert "filter" in wr .columns
343+ assert "min_wave" in wr .columns
344+ assert "max_wave" in wr .columns
345+
346+
347+ def test_get_reference_wavelength_range_other_exptype (make_datamodel ):
348+ """Test that non-SOSS exposure types return None."""
349+ model = make_datamodel .copy ()
350+ model .meta .exposure .type = "NRC_TSIMAGE"
351+ wr = WhiteLightStep ()._get_reference_wavelength_range (model )
352+ assert wr is None
353+
354+
355+ def test_get_reference_wavelength_range_no_file (make_datamodel , monkeypatch , log_watcher ):
356+ """Test that missing wavelength range reference files are handled."""
357+ model = make_datamodel .copy ()
358+ monkeypatch .setattr (WhiteLightStep , "get_reference_file" , lambda * args : "N/A" )
359+
360+ watcher = log_watcher (
361+ "jwst.white_light.white_light_step" ,
362+ message = "No wavelength range reference file found" ,
363+ level = "warning" ,
364+ )
365+ wr = WhiteLightStep ()._get_reference_wavelength_range (model )
366+ watcher .assert_seen ()
367+ assert wr is None
368+
369+
370+ def test_call_step (make_datamodel , tmp_cwd , log_watcher ):
371+ """Smoke test to ensure the step at least runs without error."""
372+ watcher = log_watcher (
373+ "jwst.white_light.white_light_step" ,
374+ message = "Using wavelength range reference file" ,
375+ level = "info" ,
376+ )
377+ result = WhiteLightStep ().call (make_datamodel , save_results = True )
378+ watcher .assert_seen ()
379+ assert isinstance (result , Table )
380+ assert Path ("step_WhiteLightStep_whtlt.ecsv" ).exists ()
0 commit comments