Skip to content

Commit f2fbcce

Browse files
authored
Merge pull request #11 from radionets-project/plotting
Add plotting submodule and px2radec function
2 parents a14d791 + e8eae4d commit f2fbcce

File tree

4 files changed

+268
-0
lines changed

4 files changed

+268
-0
lines changed

docs/changes/11.feature.rst

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
- Add plotting submodule
2+
- Add ``radiotools.plotting.px2radec`` utility function

radiotools/plotting/__init__.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
from .utils import px2radec
2+
3+
__all__ = ["px2radec"]

radiotools/plotting/utils.py

Lines changed: 93 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,93 @@
1+
import numpy as np
2+
3+
_PREFIXES = dict.fromkeys(["microarcsecond", "µas"], (3.6e9, "µas"))
4+
_PREFIXES.update(
5+
dict.fromkeys(["milliarcsecond", "marcsec", "masec", "mas"], (3.6e6, "mas"))
6+
)
7+
_PREFIXES.update(dict.fromkeys(["arcsecond", "arcsec", "asec", "as"], (3.6e3, "as")))
8+
9+
10+
def px2radec(
11+
header: dict,
12+
xlim: tuple | None = None,
13+
ylim: tuple | None = None,
14+
num_ticks: int | tuple = 9,
15+
unit: str = "mas",
16+
ax=None,
17+
):
18+
"""Converts pixels to RA/Dec relative to source reference pixel.
19+
Expects the pixel increment to be in units of deg.
20+
21+
Parameters
22+
----------
23+
header : dict or astropy.io.fits.header.Header
24+
Header corresponding to the data.
25+
xlim : tuple or None, optional
26+
Set the x-axis view limits. If set to ``None``, the full
27+
axis size is used. Default: ``None``
28+
ylim : tuple or None, optional
29+
Set the y-axis view limits. If set to ``None``, the full
30+
axis size is used. Default: ``None``
31+
num_ticks : int or tuple, optional
32+
Number of ticks on the axes. If given a tuple,
33+
element 0 correponds to the number of ticks on the
34+
x-axis, while element 1 corresponds to the y-axis.
35+
unit : str, optional
36+
The unit of the ticks. Can be one of ``'microarcsecond'``,
37+
``'µas'``, ``'milliarcsecond'``, ``'marcsec'``, ``'masec'``, ``'mas'``,
38+
``'arcsecond'``, ``'arcsec'``, ``'asec'``, ``'as'``. Default: ``'mas'``
39+
ax : matplotlib.axis.Axis, optional
40+
Matplotlib axis object to apply the conversion to. This will call
41+
the ``ax.set()`` method. Default: ``None``
42+
"""
43+
if unit not in _PREFIXES.keys():
44+
raise ValueError(f"Unknown unit! Please provide one of {_PREFIXES.keys()}")
45+
46+
px_incr = header["CDELT2"]
47+
ref_pos = header["CRPIX1"] - 1, header["CRPIX2"] - 1
48+
naxis = header["NAXIS1"], header["NAXIS2"]
49+
50+
if xlim:
51+
xlim = ref_pos[0] - xlim[0], ref_pos[0] + xlim[1]
52+
else:
53+
xlim = (0, naxis[0])
54+
if ylim:
55+
ylim = ref_pos[1] - ylim[0], ref_pos[1] + ylim[1]
56+
else:
57+
ylim = (0, naxis[1])
58+
59+
if not isinstance(num_ticks, tuple):
60+
num_ticks = num_ticks, num_ticks
61+
62+
xticks = np.linspace(*xlim, num_ticks[0], dtype=int, endpoint=True)
63+
yticks = np.linspace(*ylim, num_ticks[1], dtype=int, endpoint=True)
64+
65+
xticklabels = xticks - ref_pos[0]
66+
yticklabels = yticks - ref_pos[1]
67+
68+
xshift = xticklabels[xticklabels >= 0][0]
69+
yshift = yticklabels[yticklabels >= 0][0]
70+
71+
xticklabels -= xshift
72+
yticklabels -= yshift
73+
xticklabels = [
74+
f"{label * px_incr * _PREFIXES[unit][0]:.2f}" if label != 0 else "0.00"
75+
for label in xticklabels
76+
]
77+
yticklabels = [
78+
f"{label * px_incr * _PREFIXES[unit][0]:.2f}" if label != 0 else "0.00"
79+
for label in yticklabels
80+
]
81+
82+
if ax:
83+
ax.set(
84+
xlim=xlim,
85+
ylim=ylim,
86+
xticks=xticks,
87+
yticks=yticks,
88+
xticklabels=xticklabels,
89+
yticklabels=yticklabels,
90+
xlabel=rf"Relative RA $/\; \mathrm{{{_PREFIXES[unit][1]}}}$",
91+
ylabel=rf"Relative Dec $/\; \mathrm{{{_PREFIXES[unit][1]}}}$",
92+
)
93+
return xlim, ylim, xticks, yticks, xticklabels, yticklabels

tests/test_plotting.py

Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
import numpy as np
2+
from numpy.testing import assert_array_equal, assert_raises
3+
4+
5+
class TestPlottingUtils:
6+
def setup_class(self):
7+
self._HEADER = {
8+
"CDELT2": 2.7777778241006e-08,
9+
"CRPIX1": 1023.0,
10+
"CRPIX2": 1024.0,
11+
"NAXIS1": 2048,
12+
"NAXIS2": 2048,
13+
}
14+
15+
def test_px2radec(self):
16+
from radiotools.plotting import px2radec
17+
18+
_xlim = (0, 2048)
19+
_ylim = (0, 2048)
20+
_xticks = np.array([0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048])
21+
_yticks = np.array([0, 256, 512, 768, 1024, 1280, 1536, 1792, 2048])
22+
_xticklabels = [
23+
"-102.40",
24+
"-76.80",
25+
"-51.20",
26+
"-25.60",
27+
"0.00",
28+
"25.60",
29+
"51.20",
30+
"76.80",
31+
"102.40",
32+
]
33+
_yticklabels = [
34+
"-102.40",
35+
"-76.80",
36+
"-51.20",
37+
"-25.60",
38+
"0.00",
39+
"25.60",
40+
"51.20",
41+
"76.80",
42+
"102.40",
43+
]
44+
45+
(xlim, ylim, xticks, yticks, xticklabels, yticklabels) = px2radec(self._HEADER)
46+
47+
assert xlim == _xlim
48+
assert ylim == _ylim
49+
assert_array_equal(xticks, _xticks)
50+
assert_array_equal(yticks, _yticks)
51+
assert_array_equal(xticklabels, _xticklabels)
52+
assert_array_equal(yticklabels, _yticklabels)
53+
54+
def test_px2radec_xylim(self):
55+
from radiotools.plotting import px2radec
56+
57+
_xlim = (922, 1122)
58+
_ylim = (923, 1123)
59+
_xticks = np.array([922, 947, 972, 997, 1022, 1047, 1072, 1097, 1122])
60+
_yticks = np.array([923, 948, 973, 998, 1023, 1048, 1073, 1098, 1123])
61+
_xticklabels = [
62+
"-10.00",
63+
"-7.50",
64+
"-5.00",
65+
"-2.50",
66+
"0.00",
67+
"2.50",
68+
"5.00",
69+
"7.50",
70+
"10.00",
71+
]
72+
_yticklabels = [
73+
"-10.00",
74+
"-7.50",
75+
"-5.00",
76+
"-2.50",
77+
"0.00",
78+
"2.50",
79+
"5.00",
80+
"7.50",
81+
"10.00",
82+
]
83+
84+
(xlim, ylim, xticks, yticks, xticklabels, yticklabels) = px2radec(
85+
self._HEADER, xlim=(100, 100), ylim=(100, 100)
86+
)
87+
88+
assert xlim == _xlim
89+
assert ylim == _ylim
90+
assert_array_equal(xticks, _xticks)
91+
assert_array_equal(yticks, _yticks)
92+
assert_array_equal(xticklabels, _xticklabels)
93+
assert_array_equal(yticklabels, _yticklabels)
94+
95+
def test_px2radec_raise(self):
96+
from radiotools.plotting import px2radec
97+
98+
assert_raises(ValueError, px2radec, self._HEADER, unit="kiloarcsecond")
99+
100+
def test_px2radec_num_ticks(self):
101+
from radiotools.plotting import px2radec
102+
103+
_xticklabels = [
104+
"-102.40",
105+
"-76.80",
106+
"-51.20",
107+
"-25.60",
108+
"0.00",
109+
"25.60",
110+
"51.20",
111+
"76.80",
112+
"102.40",
113+
]
114+
_yticklabels = [
115+
"-102.40",
116+
"-76.80",
117+
"-51.20",
118+
"-25.60",
119+
"0.00",
120+
"25.60",
121+
"51.20",
122+
"76.80",
123+
"102.40",
124+
]
125+
126+
(_, _, _, _, xticklabels, yticklabels) = px2radec(
127+
self._HEADER, num_ticks=(9, 9)
128+
)
129+
130+
assert_array_equal(xticklabels, _xticklabels)
131+
assert_array_equal(yticklabels, _yticklabels)
132+
133+
def test_px2radec_axis(self):
134+
import matplotlib.pyplot as plt
135+
136+
from radiotools.plotting import px2radec
137+
138+
_xticklabels = [
139+
"-102.40",
140+
"-76.80",
141+
"-51.20",
142+
"-25.60",
143+
"0.00",
144+
"25.60",
145+
"51.20",
146+
"76.80",
147+
"102.40",
148+
]
149+
_yticklabels = [
150+
"-102.40",
151+
"-76.80",
152+
"-51.20",
153+
"-25.60",
154+
"0.00",
155+
"25.60",
156+
"51.20",
157+
"76.80",
158+
"102.40",
159+
]
160+
161+
fig, ax = plt.subplots()
162+
163+
px2radec(self._HEADER, ax=ax)
164+
165+
assert_array_equal(
166+
[txt.get_text() for txt in ax.get_xticklabels()], _xticklabels
167+
)
168+
assert_array_equal(
169+
[txt.get_text() for txt in ax.get_yticklabels()], _yticklabels
170+
)

0 commit comments

Comments
 (0)