plotastrodata 1.5.0__tar.gz → 1.6.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.5.0 → plotastrodata-1.6.0}/PKG-INFO +1 -1
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/__init__.py +1 -1
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/analysis_utils.py +50 -20
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fft_utils.py +5 -2
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fits_utils.py +15 -14
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fitting_utils.py +19 -8
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/los_utils.py +2 -1
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/other_utils.py +6 -3
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/plot_utils.py +107 -72
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/PKG-INFO +1 -1
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/LICENSE +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/README.md +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/const_utils.py +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/coord_utils.py +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/ext_utils.py +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/matrix_utils.py +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/SOURCES.txt +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/dependency_links.txt +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/not-zip-safe +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/requires.txt +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/top_level.txt +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.0}/setup.cfg +0 -0
- {plotastrodata-1.5.0 → plotastrodata-1.6.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.6.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
|
|
@@ -15,7 +15,8 @@ from plotastrodata.fitting_utils import EmceeCorner
|
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def quadrantmean(data: np.ndarray, x: np.ndarray, y: np.ndarray,
|
|
18
|
-
quadrants: str = '13'
|
|
18
|
+
quadrants: str = '13'
|
|
19
|
+
) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
19
20
|
"""Take mean between 1st and 3rd (or 2nd and 4th) quadrants.
|
|
20
21
|
|
|
21
22
|
Args:
|
|
@@ -67,6 +68,16 @@ def filled2d(data: np.ndarray, x: np.ndarray, y: np.ndarray, n: int = 1,
|
|
|
67
68
|
return d, xnew, ynew
|
|
68
69
|
|
|
69
70
|
|
|
71
|
+
def need_multipixel(method):
|
|
72
|
+
def wrapper(self, *args, **kwargs):
|
|
73
|
+
singlepixel = self.dx is None or self.dy is None
|
|
74
|
+
if singlepixel:
|
|
75
|
+
print('No pixel size.')
|
|
76
|
+
return
|
|
77
|
+
return method(self, *args, **kwargs)
|
|
78
|
+
return wrapper
|
|
79
|
+
|
|
80
|
+
|
|
70
81
|
@dataclass
|
|
71
82
|
class AstroData():
|
|
72
83
|
"""Data to be processed and parameters for processing the data.
|
|
@@ -132,23 +143,30 @@ class AstroData():
|
|
|
132
143
|
Args:
|
|
133
144
|
width (list, optional): Number of channels, y-pixels, and x-pixels for binning. Defaults to [1, 1, 1].
|
|
134
145
|
"""
|
|
135
|
-
|
|
146
|
+
w = [1] * (4 - len(width)) + list(width)
|
|
136
147
|
d = to4dim(self.data)
|
|
137
|
-
size =
|
|
138
|
-
|
|
148
|
+
size = np.array(np.shape(d))
|
|
149
|
+
w = np.array(w, dtype=int)
|
|
150
|
+
if np.any(w > size):
|
|
151
|
+
w = np.minimum(w, size)
|
|
152
|
+
ws = ', '.join([f'{s:d}' for s in w[1:]])
|
|
153
|
+
print(f'width was changed to [{ws}].')
|
|
154
|
+
newsize = size // w
|
|
139
155
|
grid = [None, self.v, self.y, self.x]
|
|
140
156
|
for n in range(4):
|
|
141
|
-
if
|
|
157
|
+
if w[n] == 1 or grid[n] is None:
|
|
142
158
|
continue
|
|
143
159
|
size[n] = newsize[n]
|
|
144
160
|
olddata = np.moveaxis(d, n, 0)
|
|
145
161
|
newdata = np.moveaxis(np.zeros(size), n, 0)
|
|
146
162
|
t = np.zeros(newsize[n])
|
|
147
|
-
for i in range(
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
163
|
+
for i in range(w[n]):
|
|
164
|
+
i_stop = i + newsize[n] * w[n]
|
|
165
|
+
i_step = w[n]
|
|
166
|
+
t += grid[n][i:i_stop:i_step]
|
|
167
|
+
newdata += olddata[i:i_stop:i_step]
|
|
168
|
+
grid[n] = t / w[n]
|
|
169
|
+
d = np.moveaxis(newdata, 0, n) / w[n]
|
|
152
170
|
self.data = np.squeeze(d)
|
|
153
171
|
_, self.v, self.y, self.x = grid
|
|
154
172
|
|
|
@@ -188,6 +206,7 @@ class AstroData():
|
|
|
188
206
|
else:
|
|
189
207
|
print('No change because includexy=False and includev=False.')
|
|
190
208
|
|
|
209
|
+
@need_multipixel
|
|
191
210
|
def circularbeam(self):
|
|
192
211
|
"""Make the beam circular by convolving with 1D Gaussian
|
|
193
212
|
"""
|
|
@@ -201,7 +220,8 @@ class AstroData():
|
|
|
201
220
|
ny = len(self.y) if len(self.y) % 2 == 1 else len(self.y) - 1
|
|
202
221
|
y = np.linspace(-(ny-1) / 2, (ny-1) / 2, ny) * np.abs(self.dy)
|
|
203
222
|
g1 = np.exp(-4*np.log(2) * y**2 / (bmaj**2 - bmin**2))
|
|
204
|
-
|
|
223
|
+
e = np.sqrt(1 - bmin**2 / bmaj**2)
|
|
224
|
+
g1 /= np.sqrt(np.pi / 4 / np.log(2) * bmin * e)
|
|
205
225
|
g = np.zeros((ny, nx))
|
|
206
226
|
g[:, (nx - 1) // 2] = g1
|
|
207
227
|
d = self.data.copy()
|
|
@@ -239,7 +259,9 @@ class AstroData():
|
|
|
239
259
|
bmin_new = 1 / np.sqrt(alpha + Det)
|
|
240
260
|
self.beam = np.array([bmaj_new, bmin_new, bpa_new])
|
|
241
261
|
|
|
242
|
-
|
|
262
|
+
@need_multipixel
|
|
263
|
+
def fit2d(self, model: object, bounds: np.ndarray,
|
|
264
|
+
progressbar: bool = False,
|
|
243
265
|
kwargs_fit: dict = {}, kwargs_plotcorner: dict = {},
|
|
244
266
|
chan: int | None = None):
|
|
245
267
|
"""Fit a given 2D model function to self.data.
|
|
@@ -257,7 +279,7 @@ class AstroData():
|
|
|
257
279
|
"""
|
|
258
280
|
d = self.data if chan is None else self.data[chan]
|
|
259
281
|
x, y = np.meshgrid(self.x, self.y)
|
|
260
|
-
if None not in self.beam
|
|
282
|
+
if None not in self.beam:
|
|
261
283
|
Omega = np.pi * self.beam[0] * self.beam[1] / 4 / np.log(2)
|
|
262
284
|
pixelperbeam = Omega / np.abs(self.dx * self.dy)
|
|
263
285
|
else:
|
|
@@ -273,8 +295,9 @@ class AstroData():
|
|
|
273
295
|
mcmc.fit(**kwargs_fit0)
|
|
274
296
|
kwargs_plotcorner0 = {'show': False, 'savefig': None}
|
|
275
297
|
kwargs_plotcorner0.update(kwargs_plotcorner)
|
|
276
|
-
|
|
277
|
-
|
|
298
|
+
kw_pl = kwargs_plotcorner0
|
|
299
|
+
if kw_pl['show'] or kw_pl['savefig'] is not None:
|
|
300
|
+
mcmc.plotcorner(**kw_pl)
|
|
278
301
|
popt = mcmc.popt
|
|
279
302
|
plow = mcmc.plow
|
|
280
303
|
pmid = mcmc.pmid
|
|
@@ -284,6 +307,7 @@ class AstroData():
|
|
|
284
307
|
return {'popt': popt, 'plow': plow, 'pmid': pmid, 'phigh': phigh,
|
|
285
308
|
'model': modelopt, 'residual': residual}
|
|
286
309
|
|
|
310
|
+
@need_multipixel
|
|
287
311
|
def gaussfit2d(self, chan: int | None = None):
|
|
288
312
|
"""Fit a 2D Gaussian function to self.data.
|
|
289
313
|
|
|
@@ -311,7 +335,8 @@ class AstroData():
|
|
|
311
335
|
p0=p0, bounds=bounds)
|
|
312
336
|
model = gaussian2d((x, y), *popt)
|
|
313
337
|
residual = d - model
|
|
314
|
-
return {'popt': popt, 'pcov': pcov,
|
|
338
|
+
return {'popt': popt, 'pcov': pcov,
|
|
339
|
+
'model': model, 'residual': residual}
|
|
315
340
|
|
|
316
341
|
def histogram(self, **kwargs) -> tuple:
|
|
317
342
|
"""Output histogram of self.data using numpy.histogram. This method can take the arguments of numpy.histogram.
|
|
@@ -415,10 +440,10 @@ class AstroData():
|
|
|
415
440
|
best, error = [None] * nprof, [None] * nprof
|
|
416
441
|
for i in range(nprof):
|
|
417
442
|
popt, pcov = curve_fit(gauss, v, prof[i], bounds=bounds)
|
|
418
|
-
|
|
443
|
+
perr = np.sqrt(np.diag(pcov))
|
|
419
444
|
print('Gauss (peak, center, FWHM):', popt)
|
|
420
|
-
print('Gauss uncertainties:',
|
|
421
|
-
best[i], error[i] = popt,
|
|
445
|
+
print('Gauss uncertainties:', perr)
|
|
446
|
+
best[i], error[i] = popt, perr
|
|
422
447
|
gfitres = {'best': best, 'error': error}
|
|
423
448
|
return v, prof, gfitres
|
|
424
449
|
|
|
@@ -445,8 +470,12 @@ class AstroData():
|
|
|
445
470
|
Returns:
|
|
446
471
|
np.ndarray: [x, data]. If self.data is 3D, the output data are in the shape of (len(v), len(x)).
|
|
447
472
|
"""
|
|
448
|
-
if dx is None:
|
|
473
|
+
if dx is None and self.dx is not None:
|
|
449
474
|
dx = np.abs(self.dx)
|
|
475
|
+
if dx is None:
|
|
476
|
+
print('dx was not found. Please input dx.')
|
|
477
|
+
return
|
|
478
|
+
|
|
450
479
|
n = int(np.ceil(length / 2 / dx))
|
|
451
480
|
r = np.linspace(-n, n, 2 * n + 1) * dx
|
|
452
481
|
pa_rad = np.radians(pa)
|
|
@@ -466,6 +495,7 @@ class AstroData():
|
|
|
466
495
|
'sigma': self.sigma, 'center': self.center, 'pv': self.pv}
|
|
467
496
|
return d
|
|
468
497
|
|
|
498
|
+
@need_multipixel
|
|
469
499
|
def writetofits(self, fitsimage: str = 'out.fits', header: dict = {}):
|
|
470
500
|
"""Write out the AstroData to a FITS file.
|
|
471
501
|
|
|
@@ -5,7 +5,8 @@ from plotastrodata.fits_utils import fits2data
|
|
|
5
5
|
from plotastrodata.plot_utils import set_rcparams
|
|
6
6
|
|
|
7
7
|
|
|
8
|
-
def shiftphase(F: np.ndarray, u: np.ndarray,
|
|
8
|
+
def shiftphase(F: np.ndarray, u: np.ndarray,
|
|
9
|
+
xoff: float = 0) -> np.ndarray:
|
|
9
10
|
"""Shift the phase of 1D FFT by xoff.
|
|
10
11
|
|
|
11
12
|
Args:
|
|
@@ -93,7 +94,9 @@ def fftcentering2(f: np.ndarray,
|
|
|
93
94
|
|
|
94
95
|
|
|
95
96
|
def ifftcentering(F: np.ndarray, u: np.ndarray | None = None,
|
|
96
|
-
xcenter: float = 0,
|
|
97
|
+
xcenter: float = 0,
|
|
98
|
+
x0: float = None,
|
|
99
|
+
outreal: bool = True
|
|
97
100
|
) -> tuple[np.ndarray, np.ndarray]:
|
|
98
101
|
"""inverse FFT with the phase referring to a specific point.
|
|
99
102
|
|
|
@@ -28,7 +28,8 @@ def Jy2K(header=None, bmaj: float | None = None, bmin: float | None = None,
|
|
|
28
28
|
bmaj, bmin = header['BMAJ'] * 3600, header['BMIN'] * 3600
|
|
29
29
|
else:
|
|
30
30
|
print('Use CDELT1^2 for Tb conversion.')
|
|
31
|
-
|
|
31
|
+
todiameter = np.sqrt(4 * np.log(2) / np.pi) * 3600
|
|
32
|
+
bmaj = bmin = header['CDELT1'] * todiameter
|
|
32
33
|
if header['CUNIT1'] == 'arcsec':
|
|
33
34
|
bmaj, bmin = bmaj / 3600, bmin / 3600
|
|
34
35
|
if 'RESTFREQ' in header:
|
|
@@ -244,21 +245,21 @@ class FitsData:
|
|
|
244
245
|
s = s + h[f'CRVAL{i:d}']
|
|
245
246
|
return s
|
|
246
247
|
|
|
247
|
-
def gen_x(
|
|
248
|
-
s = (
|
|
248
|
+
def gen_x(s_in: np.ndarray) -> None:
|
|
249
|
+
s = (s_in - cx) * dist
|
|
249
250
|
if isdeg(h['CUNIT1']):
|
|
250
251
|
s *= 3600.
|
|
251
252
|
self.x, self.dx = s, s[1] - s[0]
|
|
252
253
|
|
|
253
|
-
def gen_y(
|
|
254
|
-
s = (
|
|
254
|
+
def gen_y(s_in: np.ndarray) -> None:
|
|
255
|
+
s = (s_in - cy) * dist
|
|
255
256
|
if isdeg(h['CUNIT2']):
|
|
256
257
|
s *= 3600.
|
|
257
258
|
self.y, self.dy = s, s[1] - s[0]
|
|
258
259
|
|
|
259
|
-
def gen_v(
|
|
260
|
+
def gen_v(s_in: np.ndarray) -> None:
|
|
260
261
|
if restfreq is None:
|
|
261
|
-
freq = np.mean(
|
|
262
|
+
freq = np.mean(s_in)
|
|
262
263
|
print('restfreq is assumed to be the center.')
|
|
263
264
|
else:
|
|
264
265
|
freq = restfreq
|
|
@@ -271,28 +272,28 @@ class FitsData:
|
|
|
271
272
|
if freq == 0:
|
|
272
273
|
print('v is frequency because restfreq=0.')
|
|
273
274
|
else:
|
|
274
|
-
s = (1 -
|
|
275
|
+
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
275
276
|
case 'HZ':
|
|
276
277
|
if freq == 0:
|
|
277
278
|
print('v is frequency because restfreq=0.')
|
|
278
279
|
else:
|
|
279
|
-
s = (1 -
|
|
280
|
+
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
280
281
|
case 'm/s':
|
|
281
282
|
print(f'{key}=\'m/s\' found.')
|
|
282
|
-
s =
|
|
283
|
+
s = s_in * 1e-3 - vsys
|
|
283
284
|
case 'M/S':
|
|
284
285
|
print(f'{key}=\'M/S\' found.')
|
|
285
|
-
s =
|
|
286
|
+
s = s_in * 1e-3 - vsys
|
|
286
287
|
case 'km/s':
|
|
287
288
|
print(f'{key}=\'km/s\' found.')
|
|
288
|
-
s =
|
|
289
|
+
s = s_in - vsys
|
|
289
290
|
case 'KM/S':
|
|
290
291
|
print(f'{key}=\'KM/S\' found.')
|
|
291
|
-
s =
|
|
292
|
+
s = s_in - vsys
|
|
292
293
|
case _:
|
|
293
294
|
print(f'Unknown CUNIT3 {cunitv} found.'
|
|
294
295
|
+ ' v is read as is.')
|
|
295
|
-
s =
|
|
296
|
+
s = s_in - vsys
|
|
296
297
|
|
|
297
298
|
self.v, self.dv = s, s[1] - s[0]
|
|
298
299
|
|
|
@@ -36,7 +36,8 @@ class EmceeCorner():
|
|
|
36
36
|
|
|
37
37
|
def __init__(self, bounds: np.ndarray, logl: object | None = None,
|
|
38
38
|
model: object | None = None,
|
|
39
|
-
xdata: np.ndarray | None = None,
|
|
39
|
+
xdata: np.ndarray | None = None,
|
|
40
|
+
ydata: np.ndarray | None = None,
|
|
40
41
|
sigma: np.ndarray = 1, progressbar: bool = True,
|
|
41
42
|
percent: list = [16, 84]):
|
|
42
43
|
"""Make bounds, logl, and logp for ptemcee.
|
|
@@ -88,7 +89,8 @@ class EmceeCorner():
|
|
|
88
89
|
"""
|
|
89
90
|
global bar
|
|
90
91
|
if nwalkersperdim < 2:
|
|
91
|
-
print(
|
|
92
|
+
print('nwalkersperdim < 2 is not allowed.'
|
|
93
|
+
+ f' Use 2 instead of {nwalkersperdim:d}.')
|
|
92
94
|
nwalkers = max(nwalkersperdim, 2) * self.dim # must be even and >= 2 * dim
|
|
93
95
|
if ntemps > 1 and not pt:
|
|
94
96
|
print('ntemps>1 is supported only with pt=True. Set pt=True.')
|
|
@@ -170,7 +172,8 @@ class EmceeCorner():
|
|
|
170
172
|
print('')
|
|
171
173
|
|
|
172
174
|
def plotcorner(self, show: bool = False,
|
|
173
|
-
savefig: str | None = None,
|
|
175
|
+
savefig: str | None = None,
|
|
176
|
+
labels: list[str] | None = None,
|
|
174
177
|
cornerrange: list[float] | None = None) -> None:
|
|
175
178
|
"""Make the corner plot from self.samples.
|
|
176
179
|
|
|
@@ -184,8 +187,11 @@ class EmceeCorner():
|
|
|
184
187
|
labels = [f'Par {i:d}' for i in range(self.dim)]
|
|
185
188
|
if cornerrange is None:
|
|
186
189
|
cornerrange = self.bounds
|
|
187
|
-
corner.corner(np.reshape(self.samples, (-1, self.dim)),
|
|
188
|
-
|
|
190
|
+
corner.corner(np.reshape(self.samples, (-1, self.dim)),
|
|
191
|
+
truths=self.popt,
|
|
192
|
+
quantiles=[self.percent[0] / 100,
|
|
193
|
+
0.5,
|
|
194
|
+
self.percent[1] / 100],
|
|
189
195
|
show_titles=True, labels=labels, range=cornerrange)
|
|
190
196
|
if savefig is not None:
|
|
191
197
|
plt.savefig(savefig)
|
|
@@ -292,8 +298,10 @@ class EmceeCorner():
|
|
|
292
298
|
self.evidence = evidence
|
|
293
299
|
|
|
294
300
|
def plotongrid(self, show: bool = False, savefig: str | None = None,
|
|
295
|
-
labels: list[str] = None,
|
|
296
|
-
|
|
301
|
+
labels: list[str] = None,
|
|
302
|
+
cornerrange: list[float] = None, cmap: str = 'binary',
|
|
303
|
+
levels: list[float] = [0.011109, 0.135335, 0.606531]
|
|
304
|
+
) -> None:
|
|
297
305
|
"""Make the corner plot from the posterior calculated on a grid.
|
|
298
306
|
|
|
299
307
|
Args:
|
|
@@ -380,7 +388,10 @@ class EmceeCorner():
|
|
|
380
388
|
"""Calculate the Bayesian evidence for a model using dynamic nested sampling through dynesty.
|
|
381
389
|
"""
|
|
382
390
|
def prior_transform(u):
|
|
383
|
-
|
|
391
|
+
b0 = self.bounds[:, 0]
|
|
392
|
+
b1 = self.bounds[:, 1]
|
|
393
|
+
return b0 + (b1 - b0) * u
|
|
394
|
+
|
|
384
395
|
dsampler = DNS(loglikelihood=self.logl,
|
|
385
396
|
prior_transform=prior_transform,
|
|
386
397
|
ndim=self.dim, **kwargs)
|
|
@@ -74,7 +74,8 @@ def sys2obs(xsys: np.ndarray, ysys: np.ndarray, zsys: np.ndarray,
|
|
|
74
74
|
|
|
75
75
|
def polarvel2losvel(v_r: np.ndarray, v_theta: np.ndarray, v_phi: np.ndarray,
|
|
76
76
|
theta: np.ndarray, phi: np.ndarray,
|
|
77
|
-
incl: float = 0, phi0: float = 0, theta0: float = 90
|
|
77
|
+
incl: float = 0, phi0: float = 0, theta0: float = 90
|
|
78
|
+
) -> np.ndarray:
|
|
78
79
|
"""Convert the polar velocities in the system's coordinates to the line-of-sight velocity in the observer's coordinates. In the system coordinates, the observer is at the direction of (0, -sin i, cos i). The observer's +z (i.e., line-of-sight) is from the observer to the system. The system's x coordinate and the observer's x coordinate have opposite signs.
|
|
79
80
|
|
|
80
81
|
Args:
|
|
@@ -36,7 +36,8 @@ def isdeg(s: str) -> bool:
|
|
|
36
36
|
return False
|
|
37
37
|
|
|
38
38
|
|
|
39
|
-
def estimate_rms(data: np.ndarray, sigma: float | str | None = 'hist'
|
|
39
|
+
def estimate_rms(data: np.ndarray, sigma: float | str | None = 'hist'
|
|
40
|
+
) -> float:
|
|
40
41
|
"""Estimate a noise level of a N-D array.
|
|
41
42
|
When a float number or None is given, this function just outputs it.
|
|
42
43
|
Following methos are acceptable.
|
|
@@ -123,7 +124,8 @@ def trim(data: np.ndarray | None = None, x: np.ndarray | None = None,
|
|
|
123
124
|
xlim: list[float, float] | None = None,
|
|
124
125
|
ylim: list[float, float] | None = None,
|
|
125
126
|
vlim: list[float, float] | None = None,
|
|
126
|
-
pv: bool = False
|
|
127
|
+
pv: bool = False
|
|
128
|
+
) -> tuple[np.ndarray, list[np.ndarray, np.ndarray, np.ndarray]]:
|
|
127
129
|
"""Trim 2D or 3D data by given coordinates and their limits.
|
|
128
130
|
|
|
129
131
|
Args:
|
|
@@ -270,7 +272,8 @@ def RGIxyv(v: np.ndarray, y: np.ndarray, x: np.ndarray, data: np.ndarray,
|
|
|
270
272
|
|
|
271
273
|
def gaussian2d(xy: np.ndarray,
|
|
272
274
|
amplitude: float, xo: float, yo: float,
|
|
273
|
-
fwhm_major: float, fwhm_minor: float, pa: float
|
|
275
|
+
fwhm_major: float, fwhm_minor: float, pa: float
|
|
276
|
+
) -> np.ndarray:
|
|
274
277
|
"""Two dimensional Gaussian function.
|
|
275
278
|
|
|
276
279
|
Args:
|
|
@@ -167,8 +167,8 @@ class PlotAxes2D():
|
|
|
167
167
|
|
|
168
168
|
|
|
169
169
|
def set_minmax(data: np.ndarray, stretch: str, stretchscale: float,
|
|
170
|
-
stretchpower: float,
|
|
171
|
-
|
|
170
|
+
stretchpower: float, rms: float, kw: dict
|
|
171
|
+
) -> np.ndarray:
|
|
172
172
|
"""Set vmin and vmax for color pcolormesh and RGB maps.
|
|
173
173
|
|
|
174
174
|
Args:
|
|
@@ -203,8 +203,8 @@ def set_minmax(data: np.ndarray, stretch: str, stretchscale: float,
|
|
|
203
203
|
elif st == 'power':
|
|
204
204
|
cmin = kw['min'][i] if 'vmin' in kw else r
|
|
205
205
|
c = c.clip(cmin, None)
|
|
206
|
-
|
|
207
|
-
|
|
206
|
+
p = 1 - stretchpower
|
|
207
|
+
c = ((c / cmin)**p - 1) / p / np.log(10)
|
|
208
208
|
data[i] = c
|
|
209
209
|
n = len(data)
|
|
210
210
|
for m in ['vmin', 'vmax']:
|
|
@@ -215,13 +215,12 @@ def set_minmax(data: np.ndarray, stretch: str, stretchscale: float,
|
|
|
215
215
|
elif st == 'asinh':
|
|
216
216
|
kw[m][i] = np.arcsinh(kw[m][i] / stsc)
|
|
217
217
|
elif st == 'power':
|
|
218
|
-
|
|
219
|
-
|
|
218
|
+
p = 1 - stretchpower
|
|
219
|
+
kw[m][i] = ((kw[m][i]/c.min())**p - 1) / p / np.log(10)
|
|
220
220
|
else:
|
|
221
221
|
kw[m] = [None] * n
|
|
222
222
|
for i, (c, st, _, r) in enumerate(zip(*z)):
|
|
223
223
|
if m == 'vmin':
|
|
224
|
-
|
|
225
224
|
kw[m][i] = np.log10(r) if st == 'log' else np.nanmin(c)
|
|
226
225
|
else:
|
|
227
226
|
kw[m][i] = np.nanmax(c)
|
|
@@ -344,12 +343,16 @@ class PlotAstroData(AstroFrame):
|
|
|
344
343
|
fig (optional): External plt.figure(). Defaults to None.
|
|
345
344
|
ax (optional): External fig.add_subplot(). Defaults to None.
|
|
346
345
|
"""
|
|
347
|
-
def __init__(self,
|
|
346
|
+
def __init__(self,
|
|
347
|
+
v: np.ndarray = np.array([0]), vskip: int = 1,
|
|
348
348
|
veldigit: int = 2, restfreq: float | None = None,
|
|
349
|
-
channelnumber: int | None = None,
|
|
350
|
-
|
|
349
|
+
channelnumber: int | None = None,
|
|
350
|
+
nrows: int = 4, ncols: int = 6,
|
|
351
|
+
fontsize: int | None = None,
|
|
352
|
+
nancolor: str = 'w', dpi: int = 256,
|
|
351
353
|
figsize: tuple[float, float] | None = None,
|
|
352
|
-
fig: object | None = None, ax: object | None = None,
|
|
354
|
+
fig: object | None = None, ax: object | None = None,
|
|
355
|
+
**kwargs) -> None:
|
|
353
356
|
super().__init__(**kwargs)
|
|
354
357
|
internalfig = fig is None
|
|
355
358
|
internalax = ax is None
|
|
@@ -455,6 +458,37 @@ class PlotAstroData(AstroFrame):
|
|
|
455
458
|
return np.concatenate((d, dnan), axis=0)
|
|
456
459
|
self.vskipfill = vskipfill
|
|
457
460
|
|
|
461
|
+
def _map_init(self, kw: dict) -> tuple:
|
|
462
|
+
"""
|
|
463
|
+
Common process for add_color, add_contour, add_segment, and add_rgb.
|
|
464
|
+
xskip and yskip (int) mean spatial pixel skips, which defaults to 1.
|
|
465
|
+
|
|
466
|
+
Args:
|
|
467
|
+
kw (dict): kwargs input for each method.
|
|
468
|
+
|
|
469
|
+
Returns:
|
|
470
|
+
tuple: Data and parameters used in each method.
|
|
471
|
+
"""
|
|
472
|
+
beam_kwargs = kwargs2beamargs(kw)
|
|
473
|
+
self._kw.update(kw)
|
|
474
|
+
if 'xskip' in self._kw:
|
|
475
|
+
xskip = self._kw['xskip']
|
|
476
|
+
del self._kw['xskip']
|
|
477
|
+
else:
|
|
478
|
+
xskip = 1
|
|
479
|
+
if 'yskip' in self._kw:
|
|
480
|
+
yskip = self._kw['yskip']
|
|
481
|
+
del self._kw['yskip']
|
|
482
|
+
else:
|
|
483
|
+
yskip = 1
|
|
484
|
+
d = kwargs2AstroData(self._kw)
|
|
485
|
+
self.read(d, xskip, yskip)
|
|
486
|
+
self.beam = d.beam
|
|
487
|
+
self.sigma = d.sigma
|
|
488
|
+
singlepix = d.dx is None or d.dy is None
|
|
489
|
+
return (d.data, d.x, d.y, d.v, d.beam, d.sigma, d.bunit,
|
|
490
|
+
self._kw, beam_kwargs, singlepix)
|
|
491
|
+
|
|
458
492
|
def add_region(self, patch: str = 'ellipse',
|
|
459
493
|
poslist: list[str | list[float, float]] = [],
|
|
460
494
|
majlist: list[float] = [], minlist: list[float] = [],
|
|
@@ -666,18 +700,20 @@ class PlotAstroData(AstroFrame):
|
|
|
666
700
|
axnow.plot([x[0] - length/2., x[0] + length/2.], [y[0], y[0]],
|
|
667
701
|
'-', linewidth=linewidth, color=color)
|
|
668
702
|
|
|
669
|
-
def add_color(self,
|
|
703
|
+
def add_color(self,
|
|
670
704
|
stretch: str = 'linear',
|
|
671
705
|
stretchscale: float | None = None,
|
|
672
706
|
stretchpower: float = 0,
|
|
673
|
-
show_cbar: bool = True,
|
|
674
|
-
|
|
675
|
-
|
|
707
|
+
show_cbar: bool = True,
|
|
708
|
+
cblabel: str | None = None,
|
|
709
|
+
cbformat: float = '%.1e',
|
|
710
|
+
cbticks: list[float] | None = None,
|
|
711
|
+
cbticklabels: list[str] | None = None,
|
|
712
|
+
cblocation: str = 'right',
|
|
676
713
|
**kwargs) -> None:
|
|
677
|
-
"""Use Axes.pcolormesh of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail.
|
|
714
|
+
"""Use Axes.pcolormesh of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail. kwargs may include xskiip and yskip.
|
|
678
715
|
|
|
679
716
|
Args:
|
|
680
|
-
xskip, yskip (int, optional): Spatial pixel skip. Defaults to 1.
|
|
681
717
|
stretch (str, optional): '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'.
|
|
682
718
|
stretchscale (float, optional): color scale is asinh(data / stretchscale). Defaults to None.
|
|
683
719
|
stretchpower (float, optional): color scale is ((data / vmin)**(1 - stretchpower) - 1) / (1 - stretchpower) / ln(10). 0 means the linear scale. 1 means the logarithmic scale. Defaults to 0.
|
|
@@ -688,16 +724,14 @@ class PlotAstroData(AstroFrame):
|
|
|
688
724
|
cbticklabels (list, optional): Ticklabels of colorbar. Defaults to None.
|
|
689
725
|
cblocation (str, optional): 'left', 'top', 'left', 'right'. Only for 2D images. Defaults to 'right'.
|
|
690
726
|
"""
|
|
691
|
-
_kw = {'cmap': 'cubehelix', 'alpha': 1,
|
|
692
|
-
|
|
693
|
-
beam_kwargs
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
self.beam = beam
|
|
700
|
-
self.sigma = sigma
|
|
727
|
+
self._kw = {'cmap': 'cubehelix', 'alpha': 1,
|
|
728
|
+
'edgecolors': 'none', 'zorder': 1}
|
|
729
|
+
c, x, y, v, beam, sigma, bunit, _kw, beam_kwargs, singlepix \
|
|
730
|
+
= self._map_init(kwargs)
|
|
731
|
+
if singlepix:
|
|
732
|
+
print('No pixel size. Skip add_color.')
|
|
733
|
+
return
|
|
734
|
+
|
|
701
735
|
if stretchscale is None:
|
|
702
736
|
stretchscale = sigma
|
|
703
737
|
cmin_org = _kw['vmin'] if 'vmin' in _kw else sigma
|
|
@@ -732,8 +766,9 @@ class PlotAstroData(AstroFrame):
|
|
|
732
766
|
elif stretch == 'asinh':
|
|
733
767
|
cbticks = np.arcsinh(np.array(cbticks) / stretchscale)
|
|
734
768
|
elif stretch == 'power':
|
|
735
|
-
cbticks =
|
|
736
|
-
|
|
769
|
+
cbticks = np.array(cbticks)
|
|
770
|
+
p = 1 - stretchpower
|
|
771
|
+
cbticks = ((cbticks / cmin_org)**p - 1) / p / np.log(10)
|
|
737
772
|
cb.set_ticks(cbticks)
|
|
738
773
|
if cbticklabels is not None:
|
|
739
774
|
cb.set_ticklabels(cbticklabels)
|
|
@@ -746,28 +781,26 @@ class PlotAstroData(AstroFrame):
|
|
|
746
781
|
elif stretch == 'asinh':
|
|
747
782
|
ticklin = np.sinh(t) * stretchscale
|
|
748
783
|
elif stretch == 'power':
|
|
749
|
-
|
|
750
|
-
ticklin = cmin_org *
|
|
784
|
+
p = 1 - stretchpower
|
|
785
|
+
ticklin = cmin_org * (1 + p * np.log(10) * t)**(1 / p)
|
|
751
786
|
cb.set_ticklabels([f'{d:{cbformat[1:]}}' for d in ticklin])
|
|
752
787
|
self.add_beam(beam=beam, **beam_kwargs)
|
|
753
788
|
|
|
754
|
-
def add_contour(self,
|
|
789
|
+
def add_contour(self,
|
|
755
790
|
levels: list[float] = [-12, -6, -3, 3, 6, 12, 24, 48, 96, 192, 384],
|
|
756
791
|
**kwargs) -> None:
|
|
757
|
-
"""Use Axes.contour of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail.
|
|
792
|
+
"""Use Axes.contour of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail. kwargs may include xskiip and yskip.
|
|
758
793
|
|
|
759
794
|
Args:
|
|
760
|
-
xskip, yskip (int, optional): Spatial pixel skip. Defaults to 1.
|
|
761
795
|
levels (list, optional): Contour levels in the unit of sigma. Defaults to [-12,-6,-3,3,6,12,24,48,96,192,384].
|
|
762
796
|
"""
|
|
763
|
-
_kw = {'colors': 'gray', 'linewidths': 1.0, 'zorder': 2}
|
|
764
|
-
beam_kwargs
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
self.sigma = sigma
|
|
797
|
+
self._kw = {'colors': 'gray', 'linewidths': 1.0, 'zorder': 2}
|
|
798
|
+
c, x, y, v, beam, sigma, _, _kw, beam_kwargs, singlepix \
|
|
799
|
+
= self._map_init(kwargs)
|
|
800
|
+
if singlepix:
|
|
801
|
+
print('No pixel size. Skip add_contour.')
|
|
802
|
+
return
|
|
803
|
+
|
|
771
804
|
c = self.vskipfill(c, v)
|
|
772
805
|
if type(self.channelnumber) is int:
|
|
773
806
|
c = [c[self.channelnumber]]
|
|
@@ -775,9 +808,9 @@ class PlotAstroData(AstroFrame):
|
|
|
775
808
|
axnow.contour(x, y, cnow, np.sort(levels) * sigma, **_kw)
|
|
776
809
|
self.add_beam(beam=beam, **beam_kwargs)
|
|
777
810
|
|
|
778
|
-
def add_segment(self,
|
|
811
|
+
def add_segment(self,
|
|
812
|
+
ampfits: str = None, angfits: str = None,
|
|
779
813
|
Ufits: str = None, Qfits: str = None,
|
|
780
|
-
xskip: int = 1, yskip: int = 1,
|
|
781
814
|
amp: list[np.ndarray] | None = None,
|
|
782
815
|
ang: list[np.ndarray] | None = None,
|
|
783
816
|
stU: list[np.ndarray] | None = None,
|
|
@@ -786,14 +819,13 @@ class PlotAstroData(AstroFrame):
|
|
|
786
819
|
rotation: float = 0.,
|
|
787
820
|
cutoff: float = 3.,
|
|
788
821
|
**kwargs) -> None:
|
|
789
|
-
"""Use Axes.quiver of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. fitsimage = [ampfits, angfits, Ufits, Qfits]. data = [amp, ang, stU, stQ]. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail.
|
|
822
|
+
"""Use Axes.quiver of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. fitsimage = [ampfits, angfits, Ufits, Qfits]. data = [amp, ang, stU, stQ]. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail. kwargs may include xskiip and yskip.
|
|
790
823
|
|
|
791
824
|
Args:
|
|
792
825
|
ampfits (str, optional): In put fits name. Length of segment. Defaults to None.
|
|
793
826
|
angfits (str, optional): In put fits name. North to east. Defaults to None.
|
|
794
827
|
Ufits (str, optional): In put fits name. Stokes U. Defaults to None.
|
|
795
828
|
Qfits (str, optional): In put fits name. Stokes Q. Defaults to None.
|
|
796
|
-
xskip, yskip (int, optional): Spatial pixel skip. Defaults to 1.
|
|
797
829
|
amp (list, optional): Length of segment. Defaults to None.
|
|
798
830
|
ang (list, optional): North to east. Defaults to None.
|
|
799
831
|
stU (list, optional): Stokes U. Defaults to None.
|
|
@@ -803,16 +835,17 @@ class PlotAstroData(AstroFrame):
|
|
|
803
835
|
rotation (float, optional): Segment angle is ang + rotation. Defaults to 0..
|
|
804
836
|
cutoff (float, optional): Used when amp and ang are calculated from Stokes U and Q. In the unit of sigma. Defaults to 3..
|
|
805
837
|
"""
|
|
806
|
-
_kw = {'angles': 'xy', 'scale_units': 'xy', 'color': 'gray',
|
|
807
|
-
|
|
808
|
-
|
|
809
|
-
|
|
810
|
-
|
|
811
|
-
|
|
812
|
-
|
|
813
|
-
|
|
814
|
-
|
|
815
|
-
|
|
838
|
+
self._kw = {'angles': 'xy', 'scale_units': 'xy', 'color': 'gray',
|
|
839
|
+
'pivot': 'mid', 'headwidth': 0, 'headlength': 0,
|
|
840
|
+
'headaxislength': 0, 'width': 0.007, 'zorder': 3,
|
|
841
|
+
'fitsimage': [ampfits, angfits, Ufits, Qfits],
|
|
842
|
+
'data': [amp, ang, stU, stQ]}
|
|
843
|
+
c, x, y, v, beam, sigma, _, _kw, beam_kwargs, singlepix \
|
|
844
|
+
= self._map_init(kwargs)
|
|
845
|
+
if singlepix:
|
|
846
|
+
print('No pixel size. Skip add_segment.')
|
|
847
|
+
return
|
|
848
|
+
|
|
816
849
|
amp, ang, stU, stQ = c
|
|
817
850
|
sigmaU, sigmaQ = sigma[2:]
|
|
818
851
|
beam = [beam[i] for i in range(4) if beam[i][0] is not None][0]
|
|
@@ -834,34 +867,32 @@ class PlotAstroData(AstroFrame):
|
|
|
834
867
|
if type(self.channelnumber) is int:
|
|
835
868
|
U = [U[self.channelnumber]]
|
|
836
869
|
V = [V[self.channelnumber]]
|
|
837
|
-
_kw['scale'] = 1
|
|
870
|
+
_kw['scale'] = 1. / np.abs(x[1] - x[0])
|
|
838
871
|
for axnow, unow, vnow in zip(self.ax, U, V):
|
|
839
872
|
axnow.quiver(x, y, unow, vnow, **_kw)
|
|
840
873
|
self.add_beam(beam=beam, **beam_kwargs)
|
|
841
874
|
|
|
842
|
-
def add_rgb(self,
|
|
875
|
+
def add_rgb(self,
|
|
843
876
|
stretch: list[str, str, str] = ['linear'] * 3,
|
|
844
877
|
stretchscale: list[float | None, float | None, float | None] = [None] * 3,
|
|
845
878
|
stretchpower: float = 0,
|
|
846
879
|
**kwargs) -> None:
|
|
847
|
-
"""Use PIL.Image and imshow of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. A three-element array ([red, green, blue]) is supposed for all arguments, except for xskip, yskip and show_beam, including vmax and vmin. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail.
|
|
880
|
+
"""Use PIL.Image and imshow of matplotlib. kwargs must include the arguments of AstroData to specify the data to be plotted. A three-element array ([red, green, blue]) is supposed for all arguments, except for xskip, yskip and show_beam, including vmax and vmin. kwargs may include arguments for add_beam() and a dict of beam_kwargs to specify the beam patch in more detail. kwargs may include xskiip and yskip.
|
|
848
881
|
|
|
849
882
|
Args:
|
|
850
|
-
xskip, yskip (int, optional): Spatial pixel skip. Defaults to 1.
|
|
851
883
|
stretch (str, optional): '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'.
|
|
852
884
|
stretchscale (float, optional): color scale is asinh(data / stretchscale). Defaults to None.
|
|
853
885
|
stretchpower (float, optional): color scale is ((data / vmin)**(1 - stretchpower) - 1) / (1 - stretchpower) / ln(10). 0 means the linear scale. 1 means the logarithmic scale. Defaults to 0.
|
|
854
886
|
"""
|
|
855
887
|
from PIL import Image
|
|
856
888
|
|
|
857
|
-
_kw = {}
|
|
858
|
-
beam_kwargs
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
|
|
862
|
-
|
|
863
|
-
|
|
864
|
-
self.sigma = sigma
|
|
889
|
+
self._kw = {}
|
|
890
|
+
c, x, y, v, beam, sigma, _, _kw, beam_kwargs, singlepix \
|
|
891
|
+
= self._map_init(kwargs)
|
|
892
|
+
if singlepix:
|
|
893
|
+
print('No pixel size. Skip add_rgb.')
|
|
894
|
+
return
|
|
895
|
+
|
|
865
896
|
for i in range(len(stretchscale)):
|
|
866
897
|
if stretchscale[i] is None:
|
|
867
898
|
stretchscale[i] = sigma[i]
|
|
@@ -951,7 +982,8 @@ class PlotAstroData(AstroFrame):
|
|
|
951
982
|
xlabel: str = 'R.A. (ICRS)',
|
|
952
983
|
ylabel: str = 'Dec. (ICRS)',
|
|
953
984
|
nticksminor: int = 2,
|
|
954
|
-
grid: dict | None = None, title: dict | None = None
|
|
985
|
+
grid: dict | None = None, title: dict | None = None
|
|
986
|
+
) -> None:
|
|
955
987
|
"""Use Axes.set_* of matplotlib. kwargs can include the arguments of PlotAxes2D to adjust x and y axis.
|
|
956
988
|
|
|
957
989
|
Args:
|
|
@@ -1095,7 +1127,8 @@ def plotprofile(coords: list[str] | str = [],
|
|
|
1095
1127
|
ninterp: int = 1,
|
|
1096
1128
|
flux: bool = False, width: int = 1,
|
|
1097
1129
|
gaussfit: bool = False, gauss_kwargs: dict = {},
|
|
1098
|
-
title: list[str] | None = None,
|
|
1130
|
+
title: list[str] | None = None,
|
|
1131
|
+
text: list[str] | None = None,
|
|
1099
1132
|
dist: float = 1., vsys: float = 0.,
|
|
1100
1133
|
nrows: int = 0, ncols: int = 1, fig=None, ax=None,
|
|
1101
1134
|
getfigax: bool = False,
|
|
@@ -1365,7 +1398,8 @@ def plot3d(levels: list[float] = [3, 6, 12],
|
|
|
1365
1398
|
case 0:
|
|
1366
1399
|
shape = np.shape(d.data[0, :, :])
|
|
1367
1400
|
if np.shape(kwargs['data']) != shape:
|
|
1368
|
-
print('The shape of the 2D data is inconsistent
|
|
1401
|
+
print('The shape of the 2D data is inconsistent'
|
|
1402
|
+
+ ' with the shape of the 3D data.')
|
|
1369
1403
|
return
|
|
1370
1404
|
|
|
1371
1405
|
_kw = {'levels': [3, 6, 12, 24, 48, 96, 192, 384],
|
|
@@ -1418,4 +1452,5 @@ def plot3d(levels: list[float] = [3, 6, 12],
|
|
|
1418
1452
|
return {'data': data, 'layout': layout}
|
|
1419
1453
|
else:
|
|
1420
1454
|
fig = go.Figure(data=data, layout=layout)
|
|
1421
|
-
fig.write_html(file=outname.replace('.html', '') + '.html',
|
|
1455
|
+
fig.write_html(file=outname.replace('.html', '') + '.html',
|
|
1456
|
+
auto_play=show)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.
|
|
3
|
+
Version: 1.6.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
|
|
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
|