Public Access
phase() overwrote the underlying channel arrays (y[i] *= -1) and returned self, so rotated copies aliased the original object and negative phase angles garbled shared data. It now returns a copy with new arrays and uses numpy.deg2rad for arbitrary angles. Add unittests for phase() including negative angles and non-mutation.
98 lines
3.7 KiB
Python
98 lines
3.7 KiB
Python
import unittest
|
|
from datetime import datetime
|
|
|
|
import numpy as np
|
|
from damaris.data.ADC_Result import ADC_Result
|
|
|
|
|
|
def rotate_reference(y, degrees):
|
|
"reference rotation of the two channels by degrees"
|
|
rad = np.deg2rad(degrees)
|
|
cos, sin = np.cos(rad), np.sin(rad)
|
|
return [cos * y[0] - sin * y[1], sin * y[0] + cos * y[1]]
|
|
|
|
|
|
class TestSignalpath(unittest.TestCase):
|
|
@staticmethod
|
|
def create_adc_result():
|
|
x = np.arange(4, dtype="float32")
|
|
y = [np.array([10, 20, 30, 40], dtype="int16"),
|
|
np.array([15, 25, 35, 45], dtype="int16")]
|
|
index = [(0, 3)]
|
|
job_date = datetime(2023, 1, 1)
|
|
desc = {"key": "value"}
|
|
sampl_freq = 1000.0
|
|
job_id = 11
|
|
adc = ADC_Result(x, y, index, sampl_freq, desc, job_id, job_date)
|
|
adc.set_nChannels(2)
|
|
return adc
|
|
|
|
def assertRotated(self, adc, degrees):
|
|
rotated = adc.phase(degrees)
|
|
expected = rotate_reference(adc.y, degrees)
|
|
np.testing.assert_allclose(rotated.y[0], expected[0])
|
|
np.testing.assert_allclose(rotated.y[1], expected[1])
|
|
return rotated
|
|
|
|
def test_phase_positive_quarter_turns(self):
|
|
"""Test phase() with 0, 90, 180 and 270 degrees."""
|
|
adc = self.create_adc_result()
|
|
for degrees in (0, 90, 180, 270):
|
|
self.assertRotated(adc, degrees)
|
|
|
|
def test_phase_negative_quarter_turns(self):
|
|
"""Test phase() with negative multiples of 90 degrees."""
|
|
adc = self.create_adc_result()
|
|
for degrees in (-90, -180, -270, -360):
|
|
self.assertRotated(adc, degrees)
|
|
|
|
def test_phase_negative_arbitrary_angles(self):
|
|
"""Test phase() with arbitrary (not multiple of 90) negative angles."""
|
|
adc = self.create_adc_result()
|
|
for degrees in (-45, -120, -315):
|
|
self.assertRotated(adc, degrees)
|
|
|
|
def test_phase_equals_equivalent_positive_angle(self):
|
|
"""Test that negative angles give the same result as the equivalent positive angle."""
|
|
adc = self.create_adc_result()
|
|
for degrees in (-90, -45, -120):
|
|
np.testing.assert_allclose(adc.phase(degrees).y,
|
|
adc.phase(degrees + 360).y)
|
|
|
|
def test_phase_does_not_modify_original(self):
|
|
"""Test that phase() leaves the original data (and its arrays) untouched."""
|
|
adc = self.create_adc_result()
|
|
original = [adc.y[0].copy(), adc.y[1].copy()]
|
|
for degrees in (0, 90, 180, 270, -90, -45, 45):
|
|
rotated = adc.phase(degrees)
|
|
self.assertIsNot(rotated, adc)
|
|
self.assertIsNot(rotated.y, adc.y)
|
|
for ch in (0, 1):
|
|
self.assertIsNot(rotated.y[ch], original[ch])
|
|
np.testing.assert_array_equal(adc.y[ch], original[ch])
|
|
|
|
def test_phase_result_independent(self):
|
|
"""Test that modifying the phase-rotated copy does not affect the original."""
|
|
adc = self.create_adc_result()
|
|
rotated = adc.phase(-90)
|
|
rotated.y[0][:] = 9999
|
|
np.testing.assert_array_equal(adc.y[0], [10, 20, 30, 40])
|
|
|
|
def test_phase_chaining(self):
|
|
"""Test that chained phase() calls add up correctly."""
|
|
adc = self.create_adc_result()
|
|
np.testing.assert_allclose(adc.phase(-90).phase(-90).y,
|
|
adc.phase(-180).y)
|
|
np.testing.assert_allclose(adc.phase(-90).phase(90).y,
|
|
adc.phase(0).y)
|
|
|
|
def test_phase_wrong_channel_number(self):
|
|
"""Test the Exception raised for signalpaths with other than 2 channels."""
|
|
adc = self.create_adc_result()
|
|
adc.y.pop()
|
|
self.assertRaises(Exception, adc.phase, -90)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|