diff --git a/changelog/144.bugfix.rst b/changelog/144.bugfix.rst new file mode 100644 index 0000000..f544ff2 --- /dev/null +++ b/changelog/144.bugfix.rst @@ -0,0 +1 @@ +Fixed an issue where plotting spectrograms with non-UTC time scales (e.g., 'tt') would result in time offsets by ensuring conversion to UTC before plotting. diff --git a/pytest.ini b/pytest.ini index 0a37dc8..1bed6c3 100644 --- a/pytest.ini +++ b/pytest.ini @@ -41,3 +41,5 @@ filterwarnings = ignore:The --rsyncdir command line argument and rsyncdirs config variable are deprecated.:DeprecationWarning ignore:File may have been truncated.* ignore:pattern has been replaced with the format keyword + # pyparsing deprecation warning from old matplotlib in oldestdeps + ignore:.*deprecated:pyparsing.warnings.PyparsingDeprecationWarning diff --git a/radiospectra/mixins.py b/radiospectra/mixins.py index 6ecdfc5..f8b4fc3 100644 --- a/radiospectra/mixins.py +++ b/radiospectra/mixins.py @@ -1,3 +1,6 @@ +from astropy.visualization import time_support + + class PcolormeshPlotMixin: """ Class provides plotting functions using `~pcolormesh`. @@ -18,7 +21,6 @@ def plot(self, axes=None, **kwargs): ------- `matplotlib.collections.QuadMesh` """ - import matplotlib.dates as mdates from matplotlib import pyplot as plt if axes is None: @@ -36,17 +38,15 @@ def plot(self, axes=None, **kwargs): title = f"{title}, {self.detector}" axes.set_title(title) - axes.plot(self.times.datetime[[0, -1]], self.frequencies[[0, -1]], linestyle="None", marker="None") - if self.times.shape[0] == self.data.shape[0] and self.frequencies.shape[0] == self.data.shape[1]: - ret = axes.pcolormesh(self.times.datetime, self.frequencies.value, data, shading="auto", **kwargs) - else: - ret = axes.pcolormesh(self.times.datetime, self.frequencies.value, data[:-1, :-1], shading="auto", **kwargs) - axes.set_xlim(self.times.datetime[0], self.times.datetime[-1]) - locator = mdates.AutoDateLocator(minticks=4, maxticks=8) - formatter = mdates.ConciseDateFormatter(locator) - axes.xaxis.set_major_locator(locator) - axes.xaxis.set_major_formatter(formatter) - fig.autofmt_xdate() + with time_support(): + axes.plot(self.times[[0, -1]], self.frequencies[[0, -1]], linestyle="None", marker="None") + if self.times.shape[0] == self.data.shape[0] and self.frequencies.shape[0] == self.data.shape[1]: + ret = axes.pcolormesh(self.times, self.frequencies.value, data, shading="auto", **kwargs) + else: + ret = axes.pcolormesh(self.times, self.frequencies.value, data[:-1, :-1], shading="auto", **kwargs) + axes.set_xlim(self.times[0], self.times[-1]) + fig.autofmt_xdate() + # Set current axes/image if pyplot is being used (makes colorbar work) for i in plt.get_fignums(): if axes in plt.figure(i).axes: @@ -61,13 +61,16 @@ class NonUniformImagePlotMixin: """ def plotim(self, fig=None, axes=None, **kwargs): - import matplotlib.dates as mdates from matplotlib import pyplot as plt from matplotlib.image import NonUniformImage if axes is None: fig, axes = plt.subplots() - im = NonUniformImage(axes, interpolation="none", **kwargs) - im.set_data(mdates.date2num(self.times.datetime), self.frequencies.value, self.data) - axes.images.append(im) + with time_support(): + axes.plot(self.times[[0, -1]], self.frequencies[[0, -1]], linestyle="None", marker="None") + im = NonUniformImage(axes, interpolation="none", **kwargs) + im.set_data(axes.convert_xunits(self.times), self.frequencies.value, self.data) + axes.add_image(im) + axes.set_xlim(self.times[0], self.times[-1]) + axes.set_ylim(self.frequencies.value[0], self.frequencies.value[-1]) diff --git a/radiospectra/spectrogram/tests/conftest.py b/radiospectra/spectrogram/tests/conftest.py new file mode 100644 index 0000000..290cc21 --- /dev/null +++ b/radiospectra/spectrogram/tests/conftest.py @@ -0,0 +1,3 @@ +import matplotlib + +matplotlib.use("Agg") diff --git a/radiospectra/spectrogram/tests/test_spectrogrambase.py b/radiospectra/spectrogram/tests/test_spectrogrambase.py index e69de29..6f31d3c 100644 --- a/radiospectra/spectrogram/tests/test_spectrogrambase.py +++ b/radiospectra/spectrogram/tests/test_spectrogrambase.py @@ -0,0 +1,57 @@ +from unittest import mock + +import matplotlib.pyplot as plt +import numpy as np + +import astropy.units as u +from astropy.time import Time + +from radiospectra.spectrogram.spectrogrambase import GenericSpectrogram + + +def _get_spectrogram_with_time_scale(scale): + times = Time("2020-01-01T00:00:00", format="isot", scale=scale) + np.arange(4) * u.min + frequencies = np.linspace(10, 40, 4) * u.MHz + meta = { + "observatory": "Test", + "instrument": "TestInst", + "detector": "TestDet", + "start_time": times[0], + "end_time": times[-1], + "wavelength": np.array([1, 10]) * u.m, + "times": times, + "freqs": frequencies, + } + return GenericSpectrogram(np.arange(16).reshape(4, 4), meta) + + +def test_plot_uses_time_support_for_datetime_conversion(): + spec = _get_spectrogram_with_time_scale("tt") + + mesh = spec.plot() + x_limits = np.array(mesh.axes.get_xlim()) + expected_tt_limits = mesh.axes.convert_xunits(spec.times[[0, -1]]) + + plt.close(mesh.axes.figure) + + np.testing.assert_allclose(x_limits, expected_tt_limits) + + +def test_plotim_uses_time_support_for_datetime_conversion(): + spec = _get_spectrogram_with_time_scale("tt") + fig, axes = plt.subplots() + + with ( + mock.patch("matplotlib.image.NonUniformImage.set_interpolation", autospec=True), + mock.patch("matplotlib.image.NonUniformImage.set_data", autospec=True) as set_data, + ): + spec.plotim(fig=fig, axes=axes) + + plt.close(fig) + + _, x_values, y_values, image = set_data.call_args.args + expected_tt = axes.convert_xunits(spec.times) + + np.testing.assert_allclose(x_values, expected_tt) + np.testing.assert_allclose(y_values, spec.frequencies.value) + np.testing.assert_allclose(image, spec.data)