plotastrodata 1.9.20__tar.gz → 1.10.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plotastrodata-1.9.20/plotastrodata.egg-info → plotastrodata-1.10.0}/PKG-INFO +5 -3
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/README.md +2 -1
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/__init__.py +1 -1
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/analysis_utils.py +4 -3
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/fitting_utils.py +45 -18
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/other_utils.py +2 -2
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/plot_utils.py +129 -57
- {plotastrodata-1.9.20 → plotastrodata-1.10.0/plotastrodata.egg-info}/PKG-INFO +5 -3
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata.egg-info/requires.txt +2 -1
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/setup.cfg +2 -1
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/LICENSE +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/MANIFEST.in +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/const_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/coord_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/ext_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/fft_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/fits_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/los_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/matrix_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata/noise_utils.py +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata.egg-info/SOURCES.txt +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata.egg-info/dependency_links.txt +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata.egg-info/not-zip-safe +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/plotastrodata.egg-info/top_level.txt +0 -0
- {plotastrodata-1.9.20 → plotastrodata-1.10.0}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: plotastrodata is a tool for astronomers to create figures from FITS files and perform fundamental data analyses with ease.
|
|
5
5
|
Home-page: https://github.com/yusukeaso-astron/plotastrodata
|
|
6
6
|
Download-URL: https://github.com/yusukeaso-astron/plotastrodata
|
|
@@ -14,12 +14,13 @@ License-File: LICENSE
|
|
|
14
14
|
Requires-Dist: astropy>=7.2
|
|
15
15
|
Requires-Dist: corner
|
|
16
16
|
Requires-Dist: dynesty
|
|
17
|
-
Requires-Dist: emcee
|
|
17
|
+
Requires-Dist: emcee>=3.1.5
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
19
|
Requires-Dist: numpy>=2.0
|
|
20
20
|
Requires-Dist: pillow
|
|
21
21
|
Requires-Dist: plotly
|
|
22
22
|
Requires-Dist: ptemcee
|
|
23
|
+
Requires-Dist: pydantic>=2
|
|
23
24
|
Requires-Dist: scikit-image
|
|
24
25
|
Requires-Dist: scipy
|
|
25
26
|
Requires-Dist: tqdm
|
|
@@ -84,13 +85,14 @@ plotastrodata can do the following things.
|
|
|
84
85
|
* astropy >= 7.2
|
|
85
86
|
* corner (only for fitting)
|
|
86
87
|
* dynesty (only for fitting)
|
|
87
|
-
* emcee >= 3.
|
|
88
|
+
* emcee >= 3.1.5 (only for fitting)
|
|
88
89
|
* ffmpeg (only for movie)
|
|
89
90
|
* matplotlib
|
|
90
91
|
* numpy >= 2.0
|
|
91
92
|
* pillow (only for RGB figures)
|
|
92
93
|
* plotly (only for html cube)
|
|
93
94
|
* ptemcee (only for fitting)
|
|
95
|
+
* pydantic >= 2
|
|
94
96
|
* scikit-image (only for html cube)
|
|
95
97
|
* scipy
|
|
96
98
|
* tqdm (only for fitting)
|
|
@@ -56,13 +56,14 @@ plotastrodata can do the following things.
|
|
|
56
56
|
* astropy >= 7.2
|
|
57
57
|
* corner (only for fitting)
|
|
58
58
|
* dynesty (only for fitting)
|
|
59
|
-
* emcee >= 3.
|
|
59
|
+
* emcee >= 3.1.5 (only for fitting)
|
|
60
60
|
* ffmpeg (only for movie)
|
|
61
61
|
* matplotlib
|
|
62
62
|
* numpy >= 2.0
|
|
63
63
|
* pillow (only for RGB figures)
|
|
64
64
|
* plotly (only for html cube)
|
|
65
65
|
* ptemcee (only for fitting)
|
|
66
|
+
* pydantic >= 2
|
|
66
67
|
* scikit-image (only for html cube)
|
|
67
68
|
* scipy
|
|
68
69
|
* tqdm (only for fitting)
|
|
@@ -3,7 +3,8 @@ import warnings
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from scipy.interpolate import RegularGridInterpolator as RGI
|
|
5
5
|
from scipy.signal import convolve
|
|
6
|
-
from
|
|
6
|
+
from pydantic.dataclasses import dataclass as pydantic_dataclass
|
|
7
|
+
from typing import Any, Callable, Literal
|
|
7
8
|
|
|
8
9
|
from plotastrodata import const_utils as cu
|
|
9
10
|
from plotastrodata.coord_utils import coord2xy, rel2abs, xy2coord
|
|
@@ -621,7 +622,7 @@ ASTRODATA_ARGS = ['fitsimage', 'data', 'Tb', 'sigma', 'center', 'restfreq',
|
|
|
621
622
|
'beam_org', 'fitsheader', 'pv', 'pvpa']
|
|
622
623
|
|
|
623
624
|
|
|
624
|
-
@
|
|
625
|
+
@pydantic_dataclass
|
|
625
626
|
class AstroFrame():
|
|
626
627
|
"""Parameter set to limit and reshape the data in the AstroData format.
|
|
627
628
|
|
|
@@ -662,7 +663,7 @@ class AstroFrame():
|
|
|
662
663
|
yflip: bool = False
|
|
663
664
|
swapxy: bool = False
|
|
664
665
|
pv: bool = False
|
|
665
|
-
quadrants:
|
|
666
|
+
quadrants: Literal['13', '24'] | None = None
|
|
666
667
|
|
|
667
668
|
def __post_init__(self) -> None:
|
|
668
669
|
self.xdir = -1 if self.xflip else 1
|
|
@@ -2,9 +2,11 @@ import corner
|
|
|
2
2
|
import emcee
|
|
3
3
|
import matplotlib.pyplot as plt
|
|
4
4
|
import numpy as np
|
|
5
|
+
import pickle
|
|
5
6
|
import ptemcee
|
|
6
7
|
import warnings
|
|
7
8
|
from dynesty import DynamicNestedSampler as DNS
|
|
9
|
+
from functools import partial
|
|
8
10
|
from multiprocessing import Pool
|
|
9
11
|
from tqdm import tqdm
|
|
10
12
|
from typing import Any, Callable
|
|
@@ -18,6 +20,25 @@ bar = None
|
|
|
18
20
|
global_progressbar = True
|
|
19
21
|
|
|
20
22
|
|
|
23
|
+
def _gaussian_log_likelihood(x: np.ndarray, model: Callable,
|
|
24
|
+
xdata: np.ndarray, ydata: np.ndarray,
|
|
25
|
+
sigma: float | np.ndarray) -> float:
|
|
26
|
+
"""Return a Gaussian log likelihood using pickleable arguments."""
|
|
27
|
+
chi2 = np.sum((ydata - model(xdata, *x))**2 / sigma**2)
|
|
28
|
+
return chi2 / (-2)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _bounded_log_probability(x: np.ndarray, log_likelihood: Callable,
|
|
32
|
+
bounds: np.ndarray,
|
|
33
|
+
update_progress: bool = False) -> float:
|
|
34
|
+
"""Combine a bounded uniform prior with a log likelihood."""
|
|
35
|
+
if update_progress:
|
|
36
|
+
bar.update(1)
|
|
37
|
+
if np.all((bounds[:, 0] < x) & (x < bounds[:, 1])):
|
|
38
|
+
return log_likelihood(x)
|
|
39
|
+
return -np.inf
|
|
40
|
+
|
|
41
|
+
|
|
21
42
|
def logp(x: np.ndarray) -> float:
|
|
22
43
|
"""Log prior function made from the boundary (global_bounds) of fitting parameters.
|
|
23
44
|
|
|
@@ -95,9 +116,8 @@ class EmceeCorner():
|
|
|
95
116
|
if logl is None and (model is not None
|
|
96
117
|
and xdata is not None
|
|
97
118
|
and ydata is not None):
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
return chi2 / (-2)
|
|
119
|
+
logl = partial(_gaussian_log_likelihood, model=model,
|
|
120
|
+
xdata=xdata, ydata=ydata, sigma=sigma)
|
|
101
121
|
self.bounds = global_bounds
|
|
102
122
|
self.dim = len(self.bounds)
|
|
103
123
|
self.logl = logl
|
|
@@ -125,21 +145,27 @@ class EmceeCorner():
|
|
|
125
145
|
'logl': self.logl, 'logp': self.logp}
|
|
126
146
|
else:
|
|
127
147
|
if ncores > 1:
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
148
|
+
try:
|
|
149
|
+
pickle.dumps(self.logl)
|
|
150
|
+
except (pickle.PicklingError, AttributeError, TypeError) as exc:
|
|
151
|
+
raise TypeError(
|
|
152
|
+
'logl and model must be pickleable when ncores > 1. '
|
|
153
|
+
'Define them at module scope instead of inside another '
|
|
154
|
+
'function.') from exc
|
|
133
155
|
|
|
134
156
|
sampler_cls = emcee.EnsembleSampler
|
|
135
157
|
sampler_kwargs = {'nwalkers': nwalkers, 'ndim': self.dim,
|
|
136
|
-
'log_prob_fn':
|
|
158
|
+
'log_prob_fn': _bounded_log_probability,
|
|
159
|
+
'args': (self.logl, self.bounds,
|
|
160
|
+
global_progressbar and ncores == 1)}
|
|
137
161
|
if ncores > 1:
|
|
138
162
|
with Pool(ncores) as pool:
|
|
139
163
|
sampler = sampler_cls(**sampler_kwargs, pool=pool)
|
|
164
|
+
# This run_mcmc is duplicated so sampling finishes before the pool closes.
|
|
165
|
+
sampler.run_mcmc(pos0, nsteps)
|
|
140
166
|
else:
|
|
141
167
|
sampler = sampler_cls(**sampler_kwargs, pool=None)
|
|
142
|
-
|
|
168
|
+
sampler.run_mcmc(pos0, nsteps)
|
|
143
169
|
return sampler
|
|
144
170
|
|
|
145
171
|
def _get_samples(self, sampler: Any, nburnin: int,
|
|
@@ -190,7 +216,7 @@ class EmceeCorner():
|
|
|
190
216
|
ntry (int, optional): Number of trials for the Gelman-Rubin check. Defaults to 1.
|
|
191
217
|
pos0 (np.nparray, optional): Initial parameter set in the shape of (ntemps, nwalkers, dim). Defaults to None.
|
|
192
218
|
savechain (str, optional): File name of the chain in format of .npy. Existing files with the same name are overwritten by ``numpy.save``. Defaults to None.
|
|
193
|
-
ncores (int, optional): Number of cores for multiprocessing.Pool. ncores=1 does not use multiprocessing. Defaults to 1.
|
|
219
|
+
ncores (int, optional): Number of cores for multiprocessing.Pool. ncores=1 does not use multiprocessing. For ncores > 1, user-supplied logl and model functions must be pickleable, such as functions defined at module scope. Defaults to 1.
|
|
194
220
|
grcheck (bool, optional): Whether to check Gelman-Rubin statistics. Defaults to False.
|
|
195
221
|
pt (bool, optional): Whether to use ptemcee; otherwise, emcee is used. Defaults to False.
|
|
196
222
|
"""
|
|
@@ -243,13 +269,14 @@ class EmceeCorner():
|
|
|
243
269
|
labels = [f'Par {i:d}' for i in range(self.dim)]
|
|
244
270
|
if cornerrange is None:
|
|
245
271
|
cornerrange = self.bounds
|
|
246
|
-
corner.corner(np.reshape(self.samples, (-1, self.dim)),
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
272
|
+
fig = corner.corner(np.reshape(self.samples, (-1, self.dim)),
|
|
273
|
+
truths=self.popt,
|
|
274
|
+
quantiles=[self.percent[0] / 100,
|
|
275
|
+
0.5,
|
|
276
|
+
self.percent[1] / 100],
|
|
277
|
+
show_titles=True, labels=labels,
|
|
278
|
+
range=cornerrange)
|
|
279
|
+
close_figure(fig, savefig, show, tight=False)
|
|
253
280
|
|
|
254
281
|
def plotchain(self, labels: list | None = None, ylim: list | None = None,
|
|
255
282
|
savefig: dict | str | None = None,
|
|
@@ -296,7 +296,7 @@ def close_figure(fig: object, savefig: dict | str | None = None,
|
|
|
296
296
|
show: bool = False, tight: bool = True) -> None:
|
|
297
297
|
"""Save, show, and close the figure.
|
|
298
298
|
|
|
299
|
-
If ``savefig`` is provided, the figure is saved with Matplotlib ``Figure.savefig``. Existing files with the same name are overwritten by Matplotlib. After optional saving/showing, the figure is closed with ``plt.close()``.
|
|
299
|
+
If ``savefig`` is provided, the figure is saved with Matplotlib ``Figure.savefig``. Existing files with the same name are overwritten by Matplotlib. After optional saving/showing, the figure is closed with ``plt.close(fig)``.
|
|
300
300
|
|
|
301
301
|
Default keyword values:
|
|
302
302
|
Figure.savefig: ``bbox_inches='tight'`` and ``transparent=True``. Values in ``savefig`` override these defaults.
|
|
@@ -316,4 +316,4 @@ def close_figure(fig: object, savefig: dict | str | None = None,
|
|
|
316
316
|
fig.savefig(**savefig0)
|
|
317
317
|
if show:
|
|
318
318
|
plt.show()
|
|
319
|
-
plt.close()
|
|
319
|
+
plt.close(fig)
|
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
import matplotlib as mpl
|
|
2
2
|
import matplotlib.pyplot as plt
|
|
3
3
|
import numpy as np
|
|
4
|
-
from dataclasses import dataclass
|
|
5
4
|
from matplotlib.patches import Ellipse, Rectangle
|
|
6
|
-
from
|
|
5
|
+
from pydantic import Field, field_validator
|
|
6
|
+
from pydantic.dataclasses import dataclass as pydantic_dataclass
|
|
7
|
+
from typing import Any, Callable, Literal, TypeVar
|
|
7
8
|
|
|
8
9
|
from plotastrodata.analysis_utils import AstroData, AstroFrame
|
|
9
10
|
from plotastrodata.coord_utils import (coord2xy, xy2coord,
|
|
@@ -17,6 +18,13 @@ from plotastrodata.other_utils import (close_figure, listing,
|
|
|
17
18
|
plt.ioff() # force to turn off interactive mode
|
|
18
19
|
|
|
19
20
|
T = TypeVar('T')
|
|
21
|
+
Stretch = Literal['linear', 'log', 'asinh', 'power']
|
|
22
|
+
AxisScale = Literal['linear', 'log', 'symlog', 'asinh', 'logit']
|
|
23
|
+
FloatOrList = float | list[float]
|
|
24
|
+
OptionalFloatOrList = float | list[float | None] | None
|
|
25
|
+
BeamTriple = list[float | None]
|
|
26
|
+
BeamValue = BeamTriple | list[BeamTriple]
|
|
27
|
+
BeamPosition = list[float] | list[list[float] | None] | None
|
|
20
28
|
|
|
21
29
|
|
|
22
30
|
def set_rcparams(fontsize: int = 18, nancolor: str = 'w',
|
|
@@ -187,24 +195,24 @@ def _get_vskipfill(nv: int, v_org: np.ndarray, vskip: int,
|
|
|
187
195
|
return vskipfill
|
|
188
196
|
|
|
189
197
|
|
|
190
|
-
@
|
|
198
|
+
@pydantic_dataclass
|
|
191
199
|
class Stretcher():
|
|
192
200
|
"""Arguments and methods related to the stretch in PlotAstroData.add_color() and add_rgb().
|
|
193
201
|
|
|
194
202
|
Args:
|
|
195
203
|
stretch (str, optional): 'log', 'asinh', 'power', or 'linear'. Any other means 'linear'. 'log' means the mapped data are logarithmic. 'asinh' means the mapped data are arc sin hyperbolic. 'power' means the mapped data are power-law (see also stretchpower). Defaults to 'linear'.
|
|
196
|
-
stretchscale (float, optional): The output is asinh(data / stretchscale). Defaults to None.
|
|
197
|
-
stretchpower (float, optional): The output is data**stretchpower / stretchpower. 1 means the linear scale, while 0 means the logarithmic scale. Defaults to 0.5.
|
|
198
|
-
vmin (float, optional): The minimum value for Axes.pcolormesh() of matplotlib. Defaults to None.
|
|
199
|
-
vmax (float, optional): The maximum value for Axes.pcolormesh() of matplotlib. Defaults to None.
|
|
200
|
-
sigma (float, optional): Noise level. Defaults to 0.
|
|
204
|
+
stretchscale (float or list, optional): The output is asinh(data / stretchscale). Defaults to None.
|
|
205
|
+
stretchpower (float or list, optional): The output is data**stretchpower / stretchpower. 1 means the linear scale, while 0 means the logarithmic scale. Defaults to 0.5.
|
|
206
|
+
vmin (float or list, optional): The minimum value for Axes.pcolormesh() of matplotlib. Defaults to None.
|
|
207
|
+
vmax (float or list, optional): The maximum value for Axes.pcolormesh() of matplotlib. Defaults to None.
|
|
208
|
+
sigma (float, list, or None, optional): Noise level. None means no noise level is used. Defaults to 0.
|
|
201
209
|
"""
|
|
202
|
-
stretch:
|
|
203
|
-
stretchscale:
|
|
204
|
-
stretchpower:
|
|
205
|
-
vmin:
|
|
206
|
-
vmax:
|
|
207
|
-
sigma:
|
|
210
|
+
stretch: Stretch | list[Stretch] = 'linear'
|
|
211
|
+
stretchscale: OptionalFloatOrList = None
|
|
212
|
+
stretchpower: FloatOrList = 0.5
|
|
213
|
+
vmin: OptionalFloatOrList = None
|
|
214
|
+
vmax: OptionalFloatOrList = None
|
|
215
|
+
sigma: OptionalFloatOrList = 0
|
|
208
216
|
|
|
209
217
|
def __post_init__(self) -> None:
|
|
210
218
|
self.n = 1 if isinstance(self.stretch, str) else len(self.stretch)
|
|
@@ -301,27 +309,88 @@ class Stretcher():
|
|
|
301
309
|
return dataout, vminout, vmaxout
|
|
302
310
|
|
|
303
311
|
|
|
312
|
+
@pydantic_dataclass
|
|
304
313
|
class Beam():
|
|
305
314
|
"""Arguments for PlotAstroData.add_beam().
|
|
306
315
|
|
|
307
316
|
Args:
|
|
308
317
|
show_beam (bool, optional): Defaults to True.
|
|
309
|
-
beam (list, optional): [bmaj, bmin, bpa]
|
|
310
|
-
beamcolor (str, optional):
|
|
311
|
-
beampos (list, optional):
|
|
312
|
-
beam_kwargs (dict, optional): Additional
|
|
318
|
+
beam (list or np.ndarray, optional): One ``[bmaj, bmin, bpa]`` triple or a list of triples. ``bmaj`` and ``bmin`` must be positive; ``bpa`` is in degrees. ``[None, None, None]`` means no beam is available. Partial triples are not supported. Defaults to ``[None, None, None]``.
|
|
319
|
+
beamcolor (str or list of str, optional): Matplotlib color. One color is used for every beam; when a list is given, provide one color per beam. Defaults to ``'gray'``.
|
|
320
|
+
beampos (list or list of list, optional): One relative ``[x, y]`` position is used for every beam; when a list of positions is given, provide one position per beam. Each coordinate must be from 0 (left or bottom) to 1 (right or top). None selects the automatic position. Defaults to None.
|
|
321
|
+
beam_kwargs (dict, optional): Additional Matplotlib patch arguments. Defaults to {}.
|
|
313
322
|
"""
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
323
|
+
show_beam: bool = True
|
|
324
|
+
beam: BeamValue = Field(default_factory=lambda: [None] * 3)
|
|
325
|
+
beamcolor: str | list[str] = 'gray'
|
|
326
|
+
beampos: BeamPosition = None
|
|
327
|
+
beam_kwargs: dict[str, Any] = Field(default_factory=dict)
|
|
328
|
+
|
|
329
|
+
@field_validator('beam', mode='before')
|
|
330
|
+
@classmethod
|
|
331
|
+
def _convert_beam_arrays(cls, value: Any) -> Any:
|
|
332
|
+
"""Convert documented NumPy beam inputs to the canonical list form.
|
|
333
|
+
|
|
334
|
+
A single NumPy triple becomes a list, and NumPy triples inside a
|
|
335
|
+
list of beams are converted individually. This keeps subsequent
|
|
336
|
+
validation and plotting code independent of the input container.
|
|
337
|
+
"""
|
|
338
|
+
if isinstance(value, np.ndarray):
|
|
339
|
+
value = value.tolist()
|
|
340
|
+
if isinstance(value, list):
|
|
341
|
+
return [item.tolist() if isinstance(item, np.ndarray) else item
|
|
342
|
+
for item in value]
|
|
343
|
+
return value
|
|
344
|
+
|
|
345
|
+
@field_validator('beam')
|
|
346
|
+
@classmethod
|
|
347
|
+
def _validate_beam(cls, value: BeamValue) -> BeamValue:
|
|
348
|
+
beams = value if value and isinstance(value[0], list) else [value]
|
|
349
|
+
for beam in beams:
|
|
350
|
+
if len(beam) != 3:
|
|
351
|
+
raise ValueError('Each beam must contain [bmaj, bmin, bpa].')
|
|
352
|
+
if all(component is None for component in beam):
|
|
353
|
+
continue
|
|
354
|
+
if any(component is None for component in beam):
|
|
355
|
+
raise ValueError('A beam must be complete or all None.')
|
|
356
|
+
if not all(np.isfinite(component) for component in beam):
|
|
357
|
+
raise ValueError('Beam values must be finite.')
|
|
358
|
+
if beam[0] <= 0 or beam[1] <= 0:
|
|
359
|
+
raise ValueError('bmaj and bmin must be positive.')
|
|
360
|
+
return value
|
|
361
|
+
|
|
362
|
+
@field_validator('beampos')
|
|
363
|
+
@classmethod
|
|
364
|
+
def _validate_beampos(cls, value: BeamPosition) -> BeamPosition:
|
|
365
|
+
if value is None:
|
|
366
|
+
return value
|
|
367
|
+
positions = [value] if cls._is_position(value) else value
|
|
368
|
+
for position in positions:
|
|
369
|
+
if position is None:
|
|
370
|
+
continue
|
|
371
|
+
if len(position) != 2:
|
|
372
|
+
raise ValueError('Each beam position must contain [x, y].')
|
|
373
|
+
if not all(0 <= coordinate <= 1 for coordinate in position):
|
|
374
|
+
raise ValueError('Beam positions must be between 0 and 1.')
|
|
375
|
+
return value
|
|
376
|
+
|
|
377
|
+
@staticmethod
|
|
378
|
+
def _is_position(value: Any) -> bool:
|
|
379
|
+
return (isinstance(value, list) and len(value) == 2
|
|
380
|
+
and all(isinstance(coordinate, float)
|
|
381
|
+
for coordinate in value))
|
|
382
|
+
|
|
383
|
+
def validate_display(self) -> None:
|
|
384
|
+
"""Validate color and position lists against the number of beams."""
|
|
385
|
+
multi = self.beam and isinstance(self.beam[0], list)
|
|
386
|
+
beams = self.beam if multi else [self.beam]
|
|
387
|
+
nbeams = len(beams)
|
|
388
|
+
if isinstance(self.beamcolor, list) and len(self.beamcolor) != nbeams:
|
|
389
|
+
raise ValueError('Provide one beamcolor for each beam.')
|
|
390
|
+
if (self.beampos is not None
|
|
391
|
+
and not self._is_position(self.beampos)
|
|
392
|
+
and len(self.beampos) != nbeams):
|
|
393
|
+
raise ValueError('Provide one beampos for each beam.')
|
|
325
394
|
|
|
326
395
|
def todict(self) -> dict[str, Any]:
|
|
327
396
|
"""Return beam display settings as a dictionary.
|
|
@@ -337,13 +406,13 @@ class Beam():
|
|
|
337
406
|
return tmp
|
|
338
407
|
|
|
339
408
|
|
|
340
|
-
@
|
|
409
|
+
@pydantic_dataclass
|
|
341
410
|
class PlotAxes2D():
|
|
342
411
|
"""Use Axes.set_* to adjust x and y axes.
|
|
343
412
|
|
|
344
413
|
Args:
|
|
345
414
|
samexy (bool, optional): True supports same ticks between x and y. Defaults to True.
|
|
346
|
-
loglog (float, optional):
|
|
415
|
+
loglog (float, optional): A positive value plots on a log-log plane, with xim=(xmax / loglog, xmax) and similarly for ylim. Defaults to None.
|
|
347
416
|
xscale (str, optional): ``'log'`` labels decade ticks and ticks near the limits; other intermediate ticks are minor and unlabeled. Defaults to ``'linear'``.
|
|
348
417
|
yscale (str, optional): ``'log'`` labels decade ticks and ticks near the limits; other intermediate ticks are minor and unlabeled. Defaults to ``'linear'``.
|
|
349
418
|
xlim (list, optional): Defaults to None.
|
|
@@ -360,9 +429,9 @@ class PlotAxes2D():
|
|
|
360
429
|
aspect (dict or float, optional): Defaults to None.
|
|
361
430
|
"""
|
|
362
431
|
samexy: bool = True
|
|
363
|
-
loglog: float | None = None
|
|
364
|
-
xscale:
|
|
365
|
-
yscale:
|
|
432
|
+
loglog: float | None = Field(default=None, gt=0)
|
|
433
|
+
xscale: AxisScale = 'linear'
|
|
434
|
+
yscale: AxisScale = 'linear'
|
|
366
435
|
xlim: list | None = None
|
|
367
436
|
ylim: list | None = None
|
|
368
437
|
xlabel: str | None = None
|
|
@@ -564,13 +633,18 @@ class PlotAstroData(AstroFrame):
|
|
|
564
633
|
figsize=figsize,
|
|
565
634
|
ncols=ncols, nrows=nrows, nchan=nchan)
|
|
566
635
|
need_vlabel = nchan > 1 or animation
|
|
636
|
+
figs = []
|
|
567
637
|
for ch in range(nchan):
|
|
568
638
|
n, i, j = ch2nij(ch)
|
|
569
|
-
if
|
|
570
|
-
|
|
639
|
+
if n == len(figs):
|
|
640
|
+
if internalfig:
|
|
641
|
+
fig = plt.figure(figsize=figsize)
|
|
642
|
+
figs.append(fig)
|
|
571
643
|
if need_vlabel:
|
|
572
644
|
fig.subplots_adjust(hspace=0, wspace=0,
|
|
573
645
|
right=0.87, top=0.87)
|
|
646
|
+
else:
|
|
647
|
+
fig = figs[n]
|
|
574
648
|
if internalax:
|
|
575
649
|
sharex = ax[nij2ch(n, i - 1, j)] if i > 0 else None
|
|
576
650
|
sharey = ax[nij2ch(n, i, j - 1)] if j > 0 else None
|
|
@@ -582,6 +656,7 @@ class PlotAstroData(AstroFrame):
|
|
|
582
656
|
rf'${vlabel:.{veldigit}f}$', color='black',
|
|
583
657
|
backgroundcolor='white', zorder=20)
|
|
584
658
|
self.fig = None if internalfig else fig
|
|
659
|
+
self.figs = figs
|
|
585
660
|
self.ax = ax
|
|
586
661
|
self.rowcol = nrows * ncols
|
|
587
662
|
self.npages = npages
|
|
@@ -612,9 +687,12 @@ class PlotAstroData(AstroFrame):
|
|
|
612
687
|
self.sigma = d.sigma
|
|
613
688
|
singlepix = d.dx is None or d.dy is None
|
|
614
689
|
if len(d.beam) == 4:
|
|
615
|
-
|
|
690
|
+
beam = next((onebeam for onebeam in d.beam
|
|
691
|
+
if all(a is not None for a in onebeam)),
|
|
692
|
+
[None, None, None],)
|
|
616
693
|
else:
|
|
617
|
-
|
|
694
|
+
beam = d.beam
|
|
695
|
+
b.beam = self.beam = Beam(beam=beam).beam
|
|
618
696
|
self.add_beam(**b.todict())
|
|
619
697
|
return (d.data, d.x, d.y, d.v, d.sigma, d.bunit,
|
|
620
698
|
self._kw, singlepix)
|
|
@@ -657,8 +735,6 @@ class PlotAstroData(AstroFrame):
|
|
|
657
735
|
for ch, axnow in enumerate(self.ax):
|
|
658
736
|
if ch not in self._validchan(include_chan):
|
|
659
737
|
continue
|
|
660
|
-
if self.fig is None:
|
|
661
|
-
plt.figure(ch // self.rowcol)
|
|
662
738
|
if patch == 'rectangle':
|
|
663
739
|
a = np.radians(angle)
|
|
664
740
|
xp = x - (width*np.cos(a) + height*np.sin(a)) / 2.
|
|
@@ -674,7 +750,7 @@ class PlotAstroData(AstroFrame):
|
|
|
674
750
|
def add_beam(self, **kwargs: Any) -> None:
|
|
675
751
|
"""Use add_region() to plot the beam.
|
|
676
752
|
|
|
677
|
-
kwargs may include the arguments of Beam, except for beam_kwargs, to specify the beam appearance.
|
|
753
|
+
kwargs may include the arguments of Beam, except for beam_kwargs, to specify the beam appearance. A single ``beamcolor`` or ``beampos`` value applies to every beam; per-beam lists must provide one value for each beam. A ``[None, None, None]`` beam is skipped.
|
|
678
754
|
|
|
679
755
|
Default keyword values:
|
|
680
756
|
Beam patch: ``facecolor=beamcolor`` and ``edgecolor=None``. Other keyword arguments override these values and are passed to ``add_region``.
|
|
@@ -683,6 +759,7 @@ class PlotAstroData(AstroFrame):
|
|
|
683
759
|
show_beam, beamcolor, beampos = b.show_beam, b.beamcolor, b.beampos
|
|
684
760
|
beam = b.beam
|
|
685
761
|
del kwargs['beam']
|
|
762
|
+
b.validate_display()
|
|
686
763
|
if not show_beam:
|
|
687
764
|
return
|
|
688
765
|
|
|
@@ -691,7 +768,7 @@ class PlotAstroData(AstroFrame):
|
|
|
691
768
|
blist = [beam] if np.ndim(beam) == 1 else beam
|
|
692
769
|
n = len(blist)
|
|
693
770
|
bclist = beamcolor if isinstance(beamcolor, list) else [beamcolor] * n
|
|
694
|
-
islist = beampos
|
|
771
|
+
islist = beampos is not None and not b._is_position(beampos)
|
|
695
772
|
bplist = beampos if islist else [beampos] * n
|
|
696
773
|
for (bmaj, bmin, bpa), bc, bp in zip(blist, bclist, bplist):
|
|
697
774
|
if None in [bmaj, bmin, bpa]:
|
|
@@ -857,16 +934,13 @@ class PlotAstroData(AstroFrame):
|
|
|
857
934
|
if not show_cbar:
|
|
858
935
|
return
|
|
859
936
|
|
|
860
|
-
|
|
861
|
-
fig = plt.figure(ch // self.rowcol)
|
|
862
|
-
else:
|
|
863
|
-
fig = self.fig
|
|
937
|
+
fig = self.figs[ch // self.rowcol]
|
|
864
938
|
if len(self.ax) == 1:
|
|
865
939
|
ax = self.ax[ch]
|
|
866
940
|
cb = fig.colorbar(mappable[ch], ax=ax, label=cblabel,
|
|
867
941
|
format=cbformat, location=cblocation)
|
|
868
942
|
else:
|
|
869
|
-
cax =
|
|
943
|
+
cax = fig.add_axes([0.88, 0.105, 0.015, 0.77])
|
|
870
944
|
cb = fig.colorbar(mappable[ch], cax=cax, label=cblabel,
|
|
871
945
|
format=cbformat)
|
|
872
946
|
cb.ax.tick_params(labelsize=cbtickfontsize)
|
|
@@ -1104,14 +1178,13 @@ class PlotAstroData(AstroFrame):
|
|
|
1104
1178
|
axnow.set_ylabel('')
|
|
1105
1179
|
if len(self.ax) == 1:
|
|
1106
1180
|
if self.fig is None:
|
|
1107
|
-
|
|
1181
|
+
self.figs[0].tight_layout()
|
|
1108
1182
|
if title is not None:
|
|
1109
1183
|
if len(self.ax) > 1:
|
|
1110
1184
|
t = {'y': 0.9}
|
|
1111
1185
|
t_in = {'t': title} if isinstance(title, str) else title
|
|
1112
1186
|
t.update(t_in)
|
|
1113
|
-
for
|
|
1114
|
-
fig = plt.figure(i)
|
|
1187
|
+
for fig in self.figs:
|
|
1115
1188
|
fig.suptitle(**t)
|
|
1116
1189
|
else:
|
|
1117
1190
|
t = {'label': title} if isinstance(title, str) else title
|
|
@@ -1249,13 +1322,13 @@ class PlotAstroData(AstroFrame):
|
|
|
1249
1322
|
show: bool = False, **kwargs: Any) -> None:
|
|
1250
1323
|
"""Use savefig of matplotlib.
|
|
1251
1324
|
|
|
1252
|
-
If ``filename`` is provided, existing files with the same name are overwritten by Matplotlib.
|
|
1325
|
+
If ``filename`` is provided, existing files with the same name are overwritten by Matplotlib. After optional saving/showing, figures managed by this instance are closed.
|
|
1253
1326
|
|
|
1254
1327
|
Default keyword values:
|
|
1255
1328
|
Figure.savefig: ``transparent=True`` and ``bbox_inches='tight'``. User-supplied keyword arguments override these values.
|
|
1256
1329
|
|
|
1257
1330
|
Args:
|
|
1258
|
-
filename (str, optional): Output image file name. Existing files may be overwritten, and
|
|
1331
|
+
filename (str, optional): Output image file name. Existing files may be overwritten, and figures managed by this instance are closed after saving/showing. Defaults to None.
|
|
1259
1332
|
show (bool, optional): True means doing plt.show(). Defaults to False.
|
|
1260
1333
|
"""
|
|
1261
1334
|
_kw = {'transparent': True, 'bbox_inches': 'tight'}
|
|
@@ -1265,15 +1338,15 @@ class PlotAstroData(AstroFrame):
|
|
|
1265
1338
|
axnow.set_ylim(*self.Ylim)
|
|
1266
1339
|
if isinstance(filename, str):
|
|
1267
1340
|
ext = filename.split('.')[-1]
|
|
1268
|
-
for i in
|
|
1341
|
+
for i, fig in enumerate(self.figs):
|
|
1269
1342
|
ver = '' if self.npages == 1 else f'_{i:d}'
|
|
1270
|
-
fig = plt.figure(i)
|
|
1271
1343
|
fig.patch.set_alpha(0)
|
|
1272
1344
|
fname = filename.replace(f'.{ext}', f'{ver}.{ext}')
|
|
1273
1345
|
fig.savefig(fname, **_kw)
|
|
1274
1346
|
if show:
|
|
1275
1347
|
plt.show()
|
|
1276
|
-
|
|
1348
|
+
for fig in self.figs:
|
|
1349
|
+
plt.close(fig)
|
|
1277
1350
|
|
|
1278
1351
|
def get_figax(self) -> tuple[object, object] | None:
|
|
1279
1352
|
"""Output the external fig and ax after plotting.
|
|
@@ -1286,8 +1359,7 @@ class PlotAstroData(AstroFrame):
|
|
|
1286
1359
|
+ ' with channel maps')
|
|
1287
1360
|
return
|
|
1288
1361
|
|
|
1289
|
-
|
|
1290
|
-
return fig, self.ax[0]
|
|
1362
|
+
return self.figs[0], self.ax[0]
|
|
1291
1363
|
|
|
1292
1364
|
|
|
1293
1365
|
def _get_ylabel_profile(_kw: dict, Tb: bool, flux: bool, bunit: str
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.10.0
|
|
4
4
|
Summary: plotastrodata is a tool for astronomers to create figures from FITS files and perform fundamental data analyses with ease.
|
|
5
5
|
Home-page: https://github.com/yusukeaso-astron/plotastrodata
|
|
6
6
|
Download-URL: https://github.com/yusukeaso-astron/plotastrodata
|
|
@@ -14,12 +14,13 @@ License-File: LICENSE
|
|
|
14
14
|
Requires-Dist: astropy>=7.2
|
|
15
15
|
Requires-Dist: corner
|
|
16
16
|
Requires-Dist: dynesty
|
|
17
|
-
Requires-Dist: emcee
|
|
17
|
+
Requires-Dist: emcee>=3.1.5
|
|
18
18
|
Requires-Dist: matplotlib
|
|
19
19
|
Requires-Dist: numpy>=2.0
|
|
20
20
|
Requires-Dist: pillow
|
|
21
21
|
Requires-Dist: plotly
|
|
22
22
|
Requires-Dist: ptemcee
|
|
23
|
+
Requires-Dist: pydantic>=2
|
|
23
24
|
Requires-Dist: scikit-image
|
|
24
25
|
Requires-Dist: scipy
|
|
25
26
|
Requires-Dist: tqdm
|
|
@@ -84,13 +85,14 @@ plotastrodata can do the following things.
|
|
|
84
85
|
* astropy >= 7.2
|
|
85
86
|
* corner (only for fitting)
|
|
86
87
|
* dynesty (only for fitting)
|
|
87
|
-
* emcee >= 3.
|
|
88
|
+
* emcee >= 3.1.5 (only for fitting)
|
|
88
89
|
* ffmpeg (only for movie)
|
|
89
90
|
* matplotlib
|
|
90
91
|
* numpy >= 2.0
|
|
91
92
|
* pillow (only for RGB figures)
|
|
92
93
|
* plotly (only for html cube)
|
|
93
94
|
* ptemcee (only for fitting)
|
|
95
|
+
* pydantic >= 2
|
|
94
96
|
* scikit-image (only for html cube)
|
|
95
97
|
* scipy
|
|
96
98
|
* tqdm (only for fitting)
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|