def test_sample_slice(self): sh = StreamGenerator(self.my_source, shape=self.shape, start_time=self.start_time, sample_rate=self.sample_rate, samples_per_frame=1) sliced = sh[-400:] assert sliced.shape == (400, ) + sh.sample_shape assert abs(sliced.stop_time - sh.stop_time) < 1. * u.ns assert abs(sliced.start_time - (sh.stop_time - 400 / sh.sample_rate)) < 1. * u.ns sh.seek(-400, 2) expected = sh.read() data = sliced.read() assert np.all(data == expected) r = repr(sliced) assert r.startswith('GetSlice(ih, item=') assert '\nih: StreamGenerator(' in r
class TestResampleReal: """Tests for resampling a signal. Here used for a real signal, but subclassed below for complex. """ dtype = np.dtype('f4') full_sample_rate = 1 * u.kHz samples_per_full_frame = 4096 # Per full frame. start_time = Time('2010-11-12T13:14:15') frequency = 400. * u.kHz sideband = np.array([-1, 1]) n_frames = 3 pad = 32 # Size of response = 2*pad + 1. atol = 7e-4 # Tolerance within which we expect to reproduce signal. @classmethod def setup_class(self): # Chose signals that are not commensurate with quarter-sample offsets, # or with the frames. f_signal = (self.full_sample_rate * 2 / self.samples_per_full_frame * np.array([31.092, 65.1234])) cosine = PureTone(f_signal, self.start_time, np.pi * u.deg) # Create a stream that just contains the two tones, and one sampled # at 4 times lower rate. self.full_fh = StreamGenerator( cosine, shape=((self.samples_per_full_frame * self.n_frames, ) + self.sideband.shape), sample_rate=self.full_sample_rate, samples_per_frame=self.samples_per_full_frame, frequency=self.frequency, sideband=self.sideband, start_time=self.start_time, dtype=self.dtype) self.part_fh = StreamGenerator( cosine, shape=((self.samples_per_full_frame // 4 * self.n_frames, ) + self.sideband.shape), sample_rate=self.full_sample_rate / 4, samples_per_frame=self.samples_per_full_frame // 4, frequency=self.frequency, sideband=self.sideband, start_time=self.start_time, dtype=self.dtype) def test_setup(self): self.full_fh.seek(0) self.part_fh.seek(0) full = self.full_fh.read() part = self.part_fh.read() assert_allclose(part, full[::4], atol=1e-8, rtol=0) @pytest.mark.parametrize('offset', (34, 34.5, 35.75, 50. * u.ms, 0.065 * u.s, Time('2010-11-12T13:14:15.073'))) def test_resample(self, offset): # Offsets equal to quarter samples to allow check with full_fh. ih = Resample(self.part_fh, offset, pad=self.pad) # Always lose 2 * pad per frame. assert ih.shape[0] == self.part_fh.shape[0] - 2 * self.pad assert ih.sample_shape == self.part_fh.sample_shape # Check we are at the given offset. if isinstance(offset, Time): expected_time = offset elif isinstance(offset, u.Quantity): expected_time = self.part_fh.start_time + offset else: expected_time = (self.part_fh.start_time + offset / self.part_fh.sample_rate) assert abs(ih.time - expected_time) < 1. * u.ns part_fh_offset = seek_float(self.part_fh, offset) ioffset = round(part_fh_offset) fraction = part_fh_offset - ioffset assert ih.offset + self.pad == ioffset expected_start_time = (self.part_fh.start_time + ( (self.pad + fraction) / self.part_fh.sample_rate)) assert abs(ih.start_time - expected_start_time) < 1. * u.ns # Check that data is correctly resampled. ih.seek(0) data = ih.read() # Find corresponding fully sampled data. self.full_fh.seek(ih.start_time) # Check time: should be exact, since our offsets are quarter samples. assert np.abs(self.full_fh.time - ih.start_time) < 1. * u.ns expected = self.full_fh.read(data.shape[0] * 4)[::4] assert_allclose(data, expected, atol=self.atol, rtol=0) def test_repr(self): ih = Resample(self.part_fh, 0.5, samples_per_frame=511) r = repr(ih) assert r.startswith('Resample(ih') assert 'offset=0.5' in r @pytest.mark.parametrize('shift', (0., 0.25, -5.25, [1.75, 10.25], [-1., 13] * u.ms) ) @pytest.mark.parametrize('offset', (None, 0, 0.25)) def test_shift_and_resample(self, shift, offset): # Shifts and offsets at quarter samples to allow check with full_fh. ih = ShiftAndResample(self.part_fh, shift, offset=offset, pad=self.pad) # start_time should be at expected offset from old grid. expected_offset = seek_float( self.part_fh, offset if offset is not None else np.mean(shift)) d_off = ((ih.start_time - self.start_time) * ih.sample_rate - expected_offset).to_value(u.one) assert abs(d_off - np.around(d_off)) < u.ns * ih.sample_rate expected_length = (self.part_fh.shape[0] - 2 * self.pad - seek_float(self.part_fh, np.ptp(shift))) assert abs(ih.shape[0] - expected_length) <= 0.5 # Data should be shifted by the expected amounts. shift = np.atleast_1d(shift) for i, s in enumerate(shift): ih.seek(0) time_shift = seek_float(self.part_fh, s) / ih.sample_rate fh_pos = self.full_fh.seek(ih.time - time_shift) assert fh_pos >= 0 assert abs(ih.time - time_shift - self.full_fh.time) < 1. * u.ns data = ih.read() expected = self.full_fh.read(len(data) * 4)[::4] sel = i if shift.size > 1 else Ellipsis assert_allclose(data[:, sel], expected[:, sel], atol=self.atol, rtol=0)
class TestShiftSamples: @classmethod def make_arange_data(self, ih): test_data = (np.arange( ih.offset, ih.offset + ih.samples_per_frame).reshape((ih.samples_per_frame, ) + (1, ) * len(ih.shape[1:]))) new_shape = (ih.samples_per_frame, ) + ih.shape[1:] return np.broadcast_to(test_data, new_shape) @classmethod def make_non_uniform_arange_data(self, ih): axis = ih.non_uniform_axis data = self.make_arange_data(ih) multiplier = np.arange(1, data.shape[axis] + 1) * ih.intensity return data * multiplier.reshape((data.shape[axis], ) + (1, ) * (data.ndim - 1 - axis)) @classmethod def setup_class(self): self.shape = (1000, 5, 3) self.ih = StreamGenerator(self.make_arange_data, self.shape, Time('2010-11-12'), 1. * u.Hz, samples_per_frame=100, dtype=float) self.ih.intensity = 2 self.ih.non_uniform_axis = 1 @pytest.mark.parametrize('start, n', [(0, 5), (90, 20)]) def test_shift_back(self, start, n): shift_axis = 1 shift = np.arange(-self.shape[shift_axis] + 1, 1) assert shift.max() == 0 shifter = ShiftSamples(self.ih, shift.reshape(-1, 1), samples_per_frame=100) assert shifter.start_time == self.ih.start_time shifter.seek(start) shifted = shifter.read(n) self.ih.seek(start) raw_data = self.ih.read(100) for i, sf in enumerate(-shift): assert np.all(shifted[:, i] == raw_data[sf:sf + n, i]) @pytest.mark.parametrize('start, n', [(0, 5), (100, 20)]) def test_shift_both(self, start, n): shift = np.array([-2, 0, 3]) shifter = ShiftSamples(self.ih, shift, samples_per_frame=100) assert abs(shifter.start_time - 3 / self.ih.sample_rate - self.ih.start_time) < 1. * u.ns shifter.seek(start) shifted = shifter.read(n) self.ih.seek(start) raw_data = self.ih.read(100) for i, sf in enumerate(3 - shift): assert np.all(shifted[:, :, i] == raw_data[sf:sf + n, :, i]) def test_compare_with_shift_and_resample(self): shift = np.array([-2, 1, 4]) shifter = ShiftSamples(self.ih, shift, samples_per_frame=100) resampler = ShiftAndResample(self.ih, shift, offset=0, pad=32, samples_per_frame=200) # Note: resampler has larger padding, so start time is later. shifter.seek(90) resampler.seek(shifter.time) # integer shifts, so time should be same. assert abs(resampler.time - shifter.time) < 1. * u.ns shifted = shifter.read(20) resampled = resampler.read(20) assert_allclose(shifted, resampled) @pytest.mark.parametrize( 'fshift, ishift', [(np.array([1., 2., 3.25]), [1, 2, 3]), (np.array([[-1.9], [-5.], [5.25], [3.49], [-1.2]]), np.reshape([-2, -5, 5, 3, -1], (-1, 1)))]) def test_non_integer_shift(self, fshift, ishift): shifter1 = ShiftSamples(self.ih, fshift) comparison = ShiftSamples(self.ih, ishift) assert np.all(shifter1._shift == comparison._shift) data1 = shifter1.read() expected = comparison.read() assert np.all(data1 == expected) shifter2 = ShiftSamples(self.ih, fshift / self.ih.sample_rate) assert np.all(shifter2._shift == comparison._shift) data2 = shifter2.read() assert np.all(data2 == expected) def test_wrong_shape(self): with pytest.raises(ValueError, match='broadcast to sample shape'): ShiftSamples(self.ih, np.array([[1], [2]]))