def test_do_stage(mock_normalize): # tests that the normalized spectrum are assigned correctly. expected = 1 # make a single order spectrum flux = np.arange(1, 101) * expected x = np.arange(1, 101) spectrum = Table({ 'wavelength': [x, 2 * x], 'flux': [flux, 2 * flux], 'blaze': [flux, 2 * flux], 'blaze_error': [flux, 2 * flux], 'uncertainty': [flux, 2 * flux], 'fiber': [0, 0], 'order': [1, 2] }) image = NRESObservationFrame( [CCDData(np.zeros( (1, 1)), meta={'OBJECTS': 'tung&tung&none'})], 'test.fits') image.spectrum = Spectrum1D(spectrum) # make it so that ContinuumNormalizer.normalize just returns ones. mock_normalize.return_value = (expected * np.ones_like(flux), expected * np.ones_like(flux)) # Run the normalizer code stage = ContinuumNormalizer(SimpleNamespace(db_address='foo')) image = stage.do_stage(image) for order in [1, 2]: assert np.allclose(image.spectrum[0, order]['normflux'], expected) assert np.allclose(image.spectrum[0, order]['normuncertainty'], expected)
def test_background_subtraction_with_traces(seed): nx, ny = 405, 403 x = np.arange(nx) y = np.arange(ny) X, Y = np.meshgrid(x, y) noise_sigma = 1.0 input_background = 30 * np.exp(-(X - nx / 2.0)**2 / 300**2 - (Y - ny / 2.0 - 50.0)**2 / 200**2) test_data = input_background + np.random.normal( 0.0, noise_sigma, size=input_background.shape) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=np.ones((ny, nx)) * noise_sigma, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') test_image.traces = np.zeros((ny, nx)) for i in range(1, 8): test_image.traces[40 * i:40 * i + 10] = i input_context = context.Context({}) stage = BackgroundSubtractor(input_context) output_image = stage.do_stage(test_image) # Make sure our background estimation is good. This is roughly 4 counts which is not bad # If we fully model the image, we can do better than this, but that becomes computationally prohibitive on a 4kx4k # image np.testing.assert_allclose(output_image.background, input_background, atol=5.0)
def test_profile_fit_with_noise_with_blaze(): nx, ny = 401, 403 input_profile, trace_centers, input_traces = make_simple_traces(nx, ny, blaze=True) read_noise = 10.0 # Filter out the numerical noise input_profile[input_profile < 1e-15] = 0.0 input_profile += np.random.poisson(input_profile) input_profile += np.random.normal(0.0, read_noise, size=input_profile.shape) uncertainty = np.sqrt(input_profile + read_noise**2.0) image = NRESObservationFrame([ CCDData(data=input_profile.copy(), uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') image.traces = input_traces input_context = context.Context({}) stage = ProfileFitter(input_context) image = stage.do_stage(image) for i in range(1, np.max(image.traces) + 1): this_trace = get_trace_region(image.traces == i) scale_factor = np.max(input_profile[this_trace]) / np.max( image.profile[this_trace] * image.blaze[i - 1]['blaze']) np.testing.assert_allclose(input_profile[this_trace], image.profile[this_trace] * image.blaze[i - 1]['blaze'] * scale_factor, rtol=1.5e-2, atol=1.0)
def test_refine_traces_offset_centroid(): nx, ny = 401, 403 x2d, y2d = np.meshgrid(np.arange(nx), np.arange(ny)) test_data, trace_centers, input_traces = make_simple_traces(nx, ny) read_noise = 10.0 # Filter out the numerical noise test_data[test_data < 1e-15] = 0.0 test_data += np.random.poisson(test_data) test_data += np.random.normal(0.0, read_noise, size=test_data.shape) uncertainty = np.sqrt(test_data + read_noise**2.0) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') test_image.traces = input_traces input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceRefiner(input_context) output_image = stage.do_stage(test_image) for trace_center in trace_centers: # Make sure that the center +- 4 pixels is in the trace image assert all(output_image.traces[np.abs(y2d - trace_center + 1) <= 4])
def test_refine_traces_curved_trace(): nx, ny = 1001, 1003 expected_trace = np.zeros((ny, nx), dtype=int) trace_half_height = 5 x0 = 498 input_y_center = 2e-4 * (np.arange(nx) - x0)**2.0 + 0.01 * (np.arange(nx) - x0) + 502.0 trace = np.round(input_y_center).astype(int) for i in np.arange(nx, dtype=int): for j in range(-trace_half_height, trace_half_height + 1): expected_trace[trace[i] + j, i] = 1 x2d, y2d = np.meshgrid(np.arange(nx, dtype=int), np.arange(ny, dtype=int)) sigma = 1.5 y_center = np.array([input_y_center.copy() for i in range(ny)]) test_data = 1 / sigma / (2.0 * np.pi) * np.exp( -0.5 * (y2d - y_center)**2.0 / sigma**2) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=np.zeros_like(test_data), meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') test_image.traces = np.ones_like(expected_trace) refine_traces(test_image, weights=test_image.data, trace_half_height=trace_half_height) np.testing.assert_equal(test_image.traces, expected_trace)
def apply_master_calibration(self, image: NRESObservationFrame, master_calibration_image): # TODO should not load a master arc calibration if it is older than ~a week. image.wavelengths = master_calibration_image.wavelengths image.fibers = master_calibration_image.fibers image.meta[ 'L1IDARC'] = master_calibration_image.filename, 'ID of ARC/DOUBLE frame' return image
def test_do_stage_on_empty_features(self): input_context = context.Context({}) image = NRESObservationFrame([CCDData(data=self.data, uncertainty=self.err, meta={'OBJECTS': 'tung&tung&none'})], 'foo.fits') image.traces = np.ones_like(self.data, dtype=int) image.blaze = {'blaze': np.ones_like(self.data, dtype=int)} stage = IdentifyFeatures(input_context) stage.fwhm, stage.nsigma = self.sigma, 1.5 image = stage.do_stage(image) assert len(image.features) == 0
def generate_image(self): traces = np.array([[0, 0, 0], [1, 1, 1], [1, 1, 1], [0, 0, 0], [0, 0, 0], [2, 2, 2], [3, 3, 3]]) line_list = np.array([10, 11, 12]) pixel_positions = np.array([1, 2, 1, 2]) features = Table({'pixel': pixel_positions, 'id': np.array([1, 1, 2, 3]), 'order': np.array([1, 1, 2, 3]), 'wavelength': pixel_positions, 'centroid_err': np.ones_like(pixel_positions)}) image = NRESObservationFrame([CCDData(data=np.zeros((2, 2)), uncertainty=np.zeros((2, 2)), meta={'OBJECTS': 'tung&tung&none'})], 'foo.fits') image.features = features image.traces = traces image.line_list = line_list return image
def test_do_stage_no_blaze(self): input_context = context.Context({}) ccd_data = CCDData(data=self.data, uncertainty=self.err, meta={'OBJECTS': 'tung&tung&none'}) image = NRESObservationFrame([ccd_data], 'foo.fits') image.traces = np.ones_like(self.data, dtype=int) stage = IdentifyFeatures(input_context) stage.fwhm, stage.nsigma = self.sigma, 0.5 image = stage.do_stage(image) image.features.sort('pixel') assert np.allclose(image.features['pixel'], self.xcoords, atol=0.001) assert np.allclose(image.features['ycentroid'], self.ycoords, atol=0.001) assert np.allclose(image.features['id'], 1)
def do_stage(self, image: NRESObservationFrame): if image.profile is None: logger.error('Profile missing. Rejecting image.', image=image) return None image.add_or_update( ArrayData(np.zeros_like(image.data, dtype=float), name='WEIGHTS')) trace_ids = np.arange(1, image.num_traces + 1) for trace_id in trace_ids: yx = get_trace_region(np.isclose(image.traces, trace_id)) image.weights[yx] = self.weights(image.profile[yx], image.uncertainty[yx]**2, image.mask[yx]) return image
def test_refining_on_noisy_data(): nx, ny = 401, 403 read_noise = 10.0 test_data = np.zeros((ny, nx)) x2d, y2d = np.meshgrid(np.arange(nx), np.arange(ny)) trace_centers = [] y_0s = [100, 200, 300] for i in range(3): trace_centers.append(5e-4 * (np.arange(nx) - nx / 2.)**2 + y_0s[i]) test_data += gaussian(y2d, trace_centers[i], 3, a=10000.0) test_data += np.random.poisson(test_data) test_data += np.random.normal(0.0, read_noise, size=test_data.shape) uncertainty = np.sqrt(test_data + read_noise**2.0) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceInitializer(input_context) output_image = stage.do_stage(test_image) for trace_center in trace_centers: # Make sure that the center +- 4 pixels is in the trace image assert all(output_image.traces[np.abs(y2d - trace_center) <= 4])
def test_do_stage(self): blaze_factor = 0.5 input_context = context.Context({}) ccd_data = CCDData(data=self.data, uncertainty=self.err, meta={'OBJECTS': 'tung&tung&none'}) image = NRESObservationFrame([ccd_data], 'foo.fits') image.traces = np.ones_like(self.data, dtype=int) image.blaze = {'blaze': blaze_factor * np.ones((1, self.data.shape[1]), dtype=int)} stage = IdentifyFeatures(input_context) stage.fwhm, stage.nsigma = self.sigma, 0.5 image = stage.do_stage(image) image.features.sort('pixel') assert np.allclose(image.features['corrected_flux'], image.features['flux'] / blaze_factor, rtol=1E-4) assert np.allclose(image.features['pixel'], self.xcoords, atol=0.001) assert np.allclose(image.features['ycentroid'], self.ycoords, atol=0.001) assert np.allclose(image.features['id'], 1)
def test_get_num_lit_fibers(): image = NRESObservationFrame( [HeaderOnly(meta={'OBJECTS': 'tung&tung&none'})], 'foo.fits') assert image.num_lit_fibers() == 2 image = NRESObservationFrame( [HeaderOnly(meta={'OBJECTS': 'none&tung&none'})], 'foo.fits') assert image.num_lit_fibers() == 1
def two_order_image(): # generate 2 flat traces. traces = np.zeros((60, 20)) traces[[10, 11, 12], :] = 1 # the second trace that does not span the image entirely traces[[50, 51, 52], :] = 2 traces[[50, 51, 52], 0] = 0 traces[[50, 51, 52], -1] = 0 # generate test data with zero noise data = np.ones_like(traces, dtype=float) data[~np.isclose(traces, 0)] = 100. uncertainty = 1. * data wavelengths = (traces > 0).astype(float) * 5 image = NRESObservationFrame([ CCDData(data=data, uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') image.wavelengths = wavelengths image.traces = traces image.fibers = {'fiber': np.arange(2), 'order': np.arange(2)} image.blaze = { 'id': np.arange(2), 'blaze': [np.arange(20), np.arange(20)], 'blaze_error': [np.arange(20), np.arange(20)] } return image
def test_refine_traces_with_previous_trace(): nx, ny = 401, 403 num_traces = 3 read_noise = 10.0 test_data = np.zeros((ny, nx)) x2d, y2d = np.meshgrid(np.arange(nx), np.arange(ny)) trace_half_width = 6 trace_centers = [] y_0s = [100, 200, 300] blaze_function = 1 - 1e-5 * (x2d - nx / 2.)**2 input_traces = np.zeros((ny, nx), dtype=np.int) for i in range(num_traces): trace_centers.append(5e-4 * (np.arange(nx) - nx / 2.)**2 + y_0s[i]) test_data += gaussian(y2d, trace_centers[i], 2, a=10000.0) * blaze_function input_traces[np.abs(y2d - trace_centers[i]) <= trace_half_width] = i + 1 # Filter out the numerical noise test_data[test_data < 1e-15] = 0.0 test_data += np.random.poisson(test_data) test_data += np.random.normal(0.0, read_noise, size=test_data.shape) uncertainty = np.sqrt(test_data + read_noise**2.0) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') test_image.traces = input_traces input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceRefiner(input_context) output_image = stage.do_stage(test_image) for trace_center in trace_centers: # Make sure that the center +- 4 pixels is in the trace image assert all(output_image.traces[np.abs(y2d - trace_center) <= 4]) assert np.isclose(num_traces, output_image.num_traces)
def test_profile_fit_without_noise_without_blaze(): nx, ny = 401, 403 input_profile, trace_centers, input_traces = make_simple_traces( nx, ny, blaze=False) # Filter out the numerical noise input_profile[input_profile < 1e-15] = 0.0 uncertainty = np.sqrt(input_profile) image = NRESObservationFrame([ CCDData(data=input_profile.copy(), uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') image.traces = input_traces input_context = context.Context({}) stage = ProfileFitter(input_context) image = stage.do_stage(image) scale_factor = np.max(input_profile) / np.max(image.profile) assert np.allclose(input_profile[input_traces != 0], image.profile[input_traces != 0] * scale_factor, rtol=5e-3, atol=1.0)
def test_blind_solve(): trace_centers, test_data, x2d, y2d = make_realistic_trace_image() test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=1e-5 * np.ones_like(test_data), meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceInitializer(input_context) output_image = stage.do_stage(test_image) for trace_center in trace_centers: # Make sure that the center +- 4 pixels is in the trace image assert all(output_image.traces[np.abs(y2d - trace_center) <= 4])
def test_do_stage_does_not_fit_non_science_fiber(): # make a single order spectrum flux = np.arange(1, 101) * 2 x = np.arange(1, 101) spectrum = Table({ 'wavelength': [x], 'flux': [flux], 'blaze': [flux], 'blaze_error': [flux], 'uncertainty': [flux], 'fiber': [1], 'order': [1] }) image = NRESObservationFrame( [CCDData(np.zeros( (1, 1)), meta={'OBJECTS': 'tung&tung&none'})], 'test.fits') image.spectrum = Spectrum1D(spectrum) # Run the normalizer code stage = ContinuumNormalizer(SimpleNamespace(db_address='foo')) image = stage.do_stage(image) assert 'normflux' not in image.spectrum._table.colnames assert 'normuncertainty' not in image.spectrum._table.colnames
def test_blind_solve_with_edge_clipping_traces(): trace_centers, test_data, x2d, y2d = make_realistic_trace_image( nx=401, ny=403, y0_centers=[1, 200, 400]) test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=1e-5 * np.ones_like(test_data), meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceInitializer(input_context) output_image = stage.do_stage(test_image) assert np.count_nonzero(list(set(output_image.traces[:, 200]))) == 1 # test that the only valid trace is centered correctly assert all(output_image.traces[np.abs(y2d - trace_centers[1]) <= 4])
def test_blind_solve_with_bpm(): trace_centers, test_data, x2d, y2d = make_realistic_trace_image() bpm_mask = np.ones_like(test_data, dtype=bool) bpm_mask[ 150: 250, :] = 0 # set the pixels around the center trace as good. Leave the other pixels masked. test_image = NRESObservationFrame([ CCDData(data=test_data, uncertainty=1e-5 * np.ones_like(test_data), meta={'OBJECTS': 'tung&tung&none'}, mask=bpm_mask) ], 'foo.fits') input_context = context.Context({'TRACE_HALF_HEIGHT': 5}) stage = TraceInitializer(input_context) output_image = stage.do_stage(test_image) assert np.count_nonzero(list(set(output_image.traces[:, 200]))) == 1 # test that the only valid trace is centered correctly assert all(output_image.traces[np.abs(y2d - trace_centers[1]) <= 4])
def do_stage(self, image: NRESObservationFrame): if image.weights is None: logger.error('Extraction weights are missing. Rejecting image.', image=image) return None if image.wavelengths is None: logger.error('Wavelengths are missing. Rejecting image.', image=image) return None # consider adding a method to image, i.e. image.extracted_spectrum_shape. flux = np.zeros((image.num_traces, image.data.shape[1]), dtype=float) wavelength = np.zeros_like(flux, dtype=float) variance = np.zeros_like(flux, dtype=float) mask = np.zeros_like(flux, dtype=np.uint8) trace_ids = np.arange(1, image.num_traces + 1, dtype=int) for i, trace_id in enumerate(trace_ids): this_trace = get_trace_region(np.isclose(image.traces, trace_id)) # get the horizontal (x) extent of the trace. Consider making this a get_extent function. x_extent = slice(np.min(this_trace[1]), np.max(this_trace[1]) + 1) flux[i, x_extent] = self.extract_order(image.data[this_trace], image.weights[this_trace]) variance[i, x_extent] = self.extract_order( image.uncertainty[this_trace]**2, image.weights[this_trace]**2) # TODO: some fix that allows for actual wavelength extraction: # wavelength[i, x_extent] = self.extract_order(image.wavelengths[this_trace], # weights=1/image.wavelengths[this_trace].shape[0]) wavelength[i, x_extent] = np.median(image.wavelengths[this_trace], axis=0) mask[i, x_extent] = image.weights[this_trace].sum(axis=0) == 0.0 image.spectrum = Spectrum1D({ 'id': trace_ids, 'order': image.fibers['order'], 'fiber': image.fibers['fiber'], 'wavelength': wavelength, 'flux': flux, 'uncertainty': np.sqrt(variance), 'blaze': image.blaze['blaze'], 'blaze_error': image.blaze['blaze_error'], 'mask': mask }) return image
def test_to_fits(): data = np.ones((2, 2)) spec = Spectrum1D({ 'fiber': [0], 'order': [1], 'flux': [np.arange(10)], 'wavelength': [np.arange(10)] }) image = NRESObservationFrame([ CCDData(data=data, uncertainty=2 * data, name='SCI', meta={ 'OBJECTS': 'tung&tung&none', 'EXTNAME': '' }) ], 'foo.fits') image.add_or_update(ArrayData(3 * data, name='TRACES')) image.add_or_update(ArrayData(4 * data, name='WEIGHTS')) image.add_or_update(ArrayData(5 * data, name='BACKGROUND')) image.add_or_update(DataTable(spec.table, name='SPECTRUM')) hdulist = image.to_fits( context.Context({ 'EXTENSION_NAMES_TO_CONDENSE': ['SCI'], 'REDUCED_DATA_EXTENSION_TYPES': {}, 'fpack': True, 'LOSSLESS_EXTENSIONS': [] })) hdu_ext_names = [ hdulist[i].header.get('EXTNAME') for i in range(len(hdulist)) if hdulist[i].header.get('EXTNAME') is not None ] for name in [ 'SCI', 'BPM', 'ERR', 'TRACES', 'WEIGHTS', 'BACKGROUND', 'SPECTRUM' ]: assert name in hdu_ext_names
def five_hundred_square_image(maxflux, number_traces, trace_width, read_noise=10, seed=None): traces = np.zeros((500, 500)) data = np.ones_like(traces, dtype=float) profile = np.zeros_like(traces, dtype=float) ix = np.arange(trace_width) for i in range(1, number_traces + 1): traces[40 * i:40 * i + trace_width, :] = i for j in range(0, trace_width): data[40 * i + j, :] += maxflux * np.exp( (-1.) * (ix[j] - trace_width / 2.)**2 / (trace_width / 6.)**2) for j in range(0, trace_width): profile[40 * i + j, :] = data[40 * i + j, :] / np.sum( data[40 * i:40 * i + trace_width, 0]) np.random.seed(seed=seed) data += np.random.poisson(data) data += np.random.normal(0.0, read_noise, size=data.shape) uncertainty = np.sqrt(data + read_noise**2) wavelengths = np.ones_like( traces ) * 5 # dummy wavelengths image that has values distinct from flux and traces. image = NRESObservationFrame([ CCDData(data=data, uncertainty=uncertainty, meta={'OBJECTS': 'tung&tung&none'}) ], 'foo.fits') image.traces = traces image.profile = profile image.wavelengths = wavelengths image.blaze = { 'id': np.arange(number_traces) + 1, 'blaze': [np.ones(traces.shape[1]) for i in range(number_traces)], 'blaze_error': [np.ones(traces.shape[1]) for i in range(number_traces)] } image.fibers = { 'fiber': np.arange(number_traces) + 1, 'order': np.arange(number_traces) + 1 } return image
class TestCalculateScienceFrameMetrics: input_context = context.Context({'PIXELS_PER_RESOLUTION_ELEMENT': 4.15}) test_wavelengths = np.linspace(5100.0, 5200.0, 4096) test_flux = -0.001 * test_wavelengths**2 + 10.3 * test_wavelengths test_uncertainty = np.sqrt(test_flux) snr_order = 90 spectrum = [] header = fits.Header({'OBJECTS': 'test&none&none'}) for i in range(5): order = snr_order + i row = {'wavelength': test_wavelengths, 'flux': test_flux, 'uncertainty': test_uncertainty, 'fiber': 0, 'order': snr_order} spectrum.append(row) test_image = NRESObservationFrame([CCDData(np.zeros((1, 1)), meta=header)], 'test.fits') test_image.spectrum = Spectrum1D(spectrum) def test_do_stage_does_not_crash(self): image = CalculateScienceFrameMetrics(self.input_context).do_stage(self.test_image) assert image is not None def test_snr_calculation(self): snr, _ = get_snr(self.test_image, self.snr_order, self.input_context.PIXELS_PER_RESOLUTION_ELEMENT) snr_test = self.test_flux/self.test_uncertainty * np.sqrt(self.input_context.PIXELS_PER_RESOLUTION_ELEMENT) assert np.isclose(snr, np.max(snr_test), rtol=0.1)
def test_get_telescope_filename(): image = NRESObservationFrame([HeaderOnly(meta={'OBJECTS': 'tung&tung&none', 'TELESCOP': 'nres01'})], 'foo.fits') assert runtime_utils.get_telescope_filename(image) == 'nrs01'
def test_fiber_state_to_filename(): image = NRESObservationFrame([HeaderOnly(meta={'OBJECTS': 'tung&tung&none'})], 'foo.fits') assert fibers_state_to_filename(image) == '110'
def test_rv(mock_loader, mock_db): # parameters that define the test data num_orders = 5 lam_per_order = 70 res = 0.1 # Make fake data test_wavelengths = np.arange(4500.0, 4500 + num_orders * lam_per_order + 100, res) flux = np.ones(test_wavelengths.shape) * 1000 read_noise = 10.0 # add in fake lines for i in range(1000): central_wavelength = np.random.uniform() * 2000.0 + 4500.0 width = 0.1 # angstroms sigma = width / 2 / np.sqrt(2.0 * np.log(2.0)) flux += gaussian(test_wavelengths, central_wavelength, sigma) * 10000.0 * np.random.uniform() noisy_flux = np.random.poisson(flux).astype(float) noisy_flux += np.random.normal(0.0, read_noise, size=len(flux)) uncertainty = np.sqrt(flux + read_noise**2.0) class MockLoader: def load(self, *args): return {'wavelength': test_wavelengths, 'flux': flux} mock_loader.return_value = MockLoader() true_v = 1.205 * units.km / units.s # Make the fake image # Set the site to the north pole, and the ra and dec to the ecliptic pole. The time we chose is just # to minimize the rv correction header = fits.Header({ 'DATE-OBS': '2020-09-12T00:00:00.000000', 'RA': '18:00:00', 'DEC': '+66:33:32.8178', 'OBJECTS': 'foo&none&none', 'EXPTIME': 1200.0 }) site_info = {'longitude': 0.0, 'latitude': 90.0, 'elevation': 0.0} # Each NRES order is ~70 Angstroms wide spectrum = [] dx = int(lam_per_order / res) for i in range(5): order = slice(i * dx, (i + 1) * dx, 1) row = { 'wavelength': test_wavelengths[order] * (1.0 + true_v / constants.c), 'normflux': noisy_flux[order], 'normuncertainty': uncertainty[order], 'fiber': 0, 'order': i } spectrum.append(row) image = NRESObservationFrame([CCDData(np.zeros((1, 1)), meta=header)], 'test.fits') image.spectrum = Spectrum1D(spectrum) image.instrument = SimpleNamespace() image.instrument.site = 'npt' # Classification just can't be None so that the stage does not abort. image.classification = SimpleNamespace(**{ 'T_effective': 5000.0, 'log_g': 0.0, 'metallicity': 0.0, 'alpha': 0.0 }) mock_db.return_value = SimpleNamespace(**site_info) # Run the RV code stage = RVCalculator( SimpleNamespace(db_address='foo', MIN_ORDER_TO_CORRELATE=0, MAX_ORDER_TO_CORRELATE=num_orders - 1)) image = stage.do_stage(image) # Assert that the true_v + rv_correction is what is in the header within 5 m/s assert np.abs( true_v.to('m / s').value + image.meta['BARYCORR'] - image.meta['RV']) < 5.0