plotastrodata 1.9.9__tar.gz → 1.9.11__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.9/plotastrodata.egg-info → plotastrodata-1.9.11}/PKG-INFO +1 -1
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/__init__.py +1 -1
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/analysis_utils.py +135 -123
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/coord_utils.py +5 -5
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fits_utils.py +20 -20
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fitting_utils.py +101 -77
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/noise_utils.py +4 -4
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/other_utils.py +4 -4
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/plot_utils.py +58 -74
- {plotastrodata-1.9.9 → plotastrodata-1.9.11/plotastrodata.egg-info}/PKG-INFO +1 -1
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/LICENSE +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/MANIFEST.in +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/README.md +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/const_utils.py +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/ext_utils.py +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fft_utils.py +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/los_utils.py +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/matrix_utils.py +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/SOURCES.txt +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/dependency_links.txt +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/not-zip-safe +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/requires.txt +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/top_level.txt +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/setup.cfg +0 -0
- {plotastrodata-1.9.9 → plotastrodata-1.9.11}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.11
|
|
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
|
|
@@ -33,18 +33,19 @@ def quadrantmean(data: np.ndarray, x: np.ndarray, y: np.ndarray,
|
|
|
33
33
|
print('data must be 2D.')
|
|
34
34
|
return
|
|
35
35
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
else
|
|
47
|
-
|
|
36
|
+
if quadrants not in ['13', '24']:
|
|
37
|
+
print("quadrants must be '13' or '24'.")
|
|
38
|
+
return
|
|
39
|
+
|
|
40
|
+
dx = x[1] - x[0]
|
|
41
|
+
dy = y[1] - y[0]
|
|
42
|
+
nx = int(np.ceil(np.max(np.abs(x)) / dx))
|
|
43
|
+
ny = int(np.ceil(np.max(np.abs(y)) / dy))
|
|
44
|
+
xnew = np.linspace(-nx, nx, 2 * nx + 1) * dx
|
|
45
|
+
ynew = np.linspace(-ny, ny, 2 * ny + 1) * dy
|
|
46
|
+
s = 1 if quadrants == '13' else -1
|
|
47
|
+
f = RGI((y, s * x), data, bounds_error=False, fill_value=np.nan)
|
|
48
|
+
datanew = f(np.meshgrid(ynew, xnew, indexing='ij'))
|
|
48
49
|
datanew = (datanew + datanew[::-1, ::-1]) / 2.
|
|
49
50
|
return datanew[ny:, nx:], xnew[nx:], ynew[ny:]
|
|
50
51
|
|
|
@@ -70,12 +71,12 @@ def filled2d(data: np.ndarray, x: np.ndarray, y: np.ndarray, n: int = 1,
|
|
|
70
71
|
|
|
71
72
|
|
|
72
73
|
def _need_multipixels(method):
|
|
73
|
-
def wrapper(
|
|
74
|
-
singlepixel =
|
|
74
|
+
def wrapper(cls, *args, **kwargs):
|
|
75
|
+
singlepixel = cls.dx is None or cls.dy is None
|
|
75
76
|
if singlepixel:
|
|
76
77
|
print('No pixel size.')
|
|
77
78
|
return
|
|
78
|
-
return method(
|
|
79
|
+
return method(cls, *args, **kwargs)
|
|
79
80
|
return wrapper
|
|
80
81
|
|
|
81
82
|
|
|
@@ -117,7 +118,7 @@ class AstroData():
|
|
|
117
118
|
def __post_init__(self):
|
|
118
119
|
n = 0
|
|
119
120
|
if self.fitsimage is not None:
|
|
120
|
-
if
|
|
121
|
+
if not isinstance(self.fitsimage, list):
|
|
121
122
|
n = 1
|
|
122
123
|
elif any(a is not None for a in self.fitsimage):
|
|
123
124
|
n = len(self.fitsimage)
|
|
@@ -126,7 +127,7 @@ class AstroData():
|
|
|
126
127
|
if n > 0:
|
|
127
128
|
self.data = None
|
|
128
129
|
if self.data is not None:
|
|
129
|
-
if
|
|
130
|
+
if not isinstance(self.data, list):
|
|
130
131
|
n = 1
|
|
131
132
|
elif any(a is not None for a in self.data):
|
|
132
133
|
n = len(self.data)
|
|
@@ -140,57 +141,60 @@ class AstroData():
|
|
|
140
141
|
self.beam_org = None
|
|
141
142
|
self.fitsheader = None
|
|
142
143
|
|
|
143
|
-
def
|
|
144
|
+
def _binning_one(self, t: str, width: float):
|
|
145
|
+
grid = getattr(self, t)
|
|
146
|
+
if width == 1 or grid is None:
|
|
147
|
+
return
|
|
148
|
+
|
|
149
|
+
dt = f'd{t}'
|
|
150
|
+
sep = getattr(self, dt)
|
|
151
|
+
if sep is None:
|
|
152
|
+
s = f'Skip binning in the {t}-axis because {dt} is None.'
|
|
153
|
+
warnings.warn(s, UserWarning)
|
|
154
|
+
return
|
|
155
|
+
|
|
156
|
+
i = {'v': 1, 'y': 2, 'x': 3}[t]
|
|
157
|
+
sizenew = self.size[i] // width
|
|
158
|
+
self.size[i] = sizenew
|
|
159
|
+
data = np.moveaxis(self.data, i, 0)
|
|
160
|
+
datanew = np.moveaxis(np.zeros(self.size), i, 0)
|
|
161
|
+
gridnew = np.zeros(sizenew)
|
|
162
|
+
for start in range(width):
|
|
163
|
+
stop = start + sizenew * width
|
|
164
|
+
datanew += data[start:stop:width]
|
|
165
|
+
gridnew += grid[start:stop:width]
|
|
166
|
+
self.data = np.moveaxis(datanew, 0, i) / width
|
|
167
|
+
setattr(self, t, gridnew / width)
|
|
168
|
+
setattr(self, dt, sep * width)
|
|
169
|
+
|
|
170
|
+
def binning(self, width: list[int] = [1, 1, 1]):
|
|
144
171
|
"""Binning up neighboring pixels in the v, y, and x domain.
|
|
145
172
|
|
|
146
173
|
Args:
|
|
147
174
|
width (list, optional): Number of channels, y-pixels, and x-pixels for binning. Defaults to [1, 1, 1].
|
|
148
175
|
"""
|
|
149
|
-
w = [1] * (
|
|
176
|
+
w = np.array([1] * (3 - len(width)) + list(width), dtype=int)
|
|
150
177
|
if self.pv:
|
|
151
|
-
w[
|
|
152
|
-
w[
|
|
153
|
-
|
|
154
|
-
size = np.array(np.shape(
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
ws = ', '.join([f'{s:d}' for s in w[1:]])
|
|
178
|
+
w[1] = max(w[0], w[1])
|
|
179
|
+
w[2] = 1
|
|
180
|
+
self.data = to4dim(self.data)
|
|
181
|
+
size = np.array(np.shape(self.data))
|
|
182
|
+
if np.any(w > size[1:]):
|
|
183
|
+
w = np.minimum(w, size[1:])
|
|
184
|
+
ws = ', '.join([f'{s:d}' for s in w])
|
|
159
185
|
print(f'width was changed to [{ws}].')
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
width_v = w[2] if self.pv else w[1]
|
|
186
|
+
if (not self.pv and w[0] > 1) or (self.pv and w[1] > 1):
|
|
187
|
+
width_v = w[1] if self.pv else w[0]
|
|
163
188
|
print(f'sigma has been divided by sqrt({width_v:d})'
|
|
164
189
|
+ ' because of binning in the v-axis.')
|
|
165
190
|
self.sigma = self.sigma / np.sqrt(width_v)
|
|
166
|
-
if (not self.pv and w[
|
|
191
|
+
if (not self.pv and w[1] > 1) or w[2] > 1:
|
|
167
192
|
print('Binning in the x- or y-axis does not update sigma.')
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if dgrid[n] is None:
|
|
174
|
-
t = ['v', 'y', 'x'][n - 1]
|
|
175
|
-
s = f'Skip binning in the {t}-axis' \
|
|
176
|
-
+ f' because d{t} is None.'
|
|
177
|
-
warnings.warn(s, UserWarning)
|
|
178
|
-
continue
|
|
179
|
-
size[n] = newsize[n]
|
|
180
|
-
olddata = np.moveaxis(d, n, 0)
|
|
181
|
-
newdata = np.moveaxis(np.zeros(size), n, 0)
|
|
182
|
-
t = np.zeros(newsize[n])
|
|
183
|
-
for i in range(w[n]):
|
|
184
|
-
i_stop = i + newsize[n] * w[n]
|
|
185
|
-
i_step = w[n]
|
|
186
|
-
t += grid[n][i:i_stop:i_step]
|
|
187
|
-
newdata += olddata[i:i_stop:i_step]
|
|
188
|
-
grid[n] = t / w[n]
|
|
189
|
-
dgrid[n] = dgrid[n] * w[n]
|
|
190
|
-
d = np.moveaxis(newdata, 0, n) / w[n]
|
|
191
|
-
self.data = np.squeeze(d)
|
|
192
|
-
_, self.v, self.y, self.x = grid
|
|
193
|
-
_, self.dv, self.dy, self.dx = dgrid
|
|
193
|
+
self.size = size
|
|
194
|
+
for t, ww in zip(['v', 'y', 'x'], w):
|
|
195
|
+
self._binning_one(t, ww)
|
|
196
|
+
self.data = np.squeeze(self.data)
|
|
197
|
+
del self.size
|
|
194
198
|
if self.pv:
|
|
195
199
|
self.v = self.y
|
|
196
200
|
self.dv = self.dy
|
|
@@ -245,16 +249,14 @@ class AstroData():
|
|
|
245
249
|
nx = len(self.x) if len(self.x) % 2 == 1 else len(self.x) - 1
|
|
246
250
|
ny = len(self.y) if len(self.y) % 2 == 1 else len(self.y) - 1
|
|
247
251
|
y = np.linspace(-(ny-1) / 2, (ny-1) / 2, ny) * np.abs(self.dy)
|
|
248
|
-
g1 = np.exp(-4*np.log(2) * y**2 / (bmaj**2 - bmin**2))
|
|
252
|
+
g1 = np.exp(-4 * np.log(2) * y**2 / (bmaj**2 - bmin**2))
|
|
249
253
|
e = np.sqrt(1 - bmin**2 / bmaj**2)
|
|
250
254
|
g1 /= np.sqrt(np.pi / 4 / np.log(2) * bmin * e)
|
|
251
255
|
g = np.zeros((ny, nx))
|
|
252
256
|
g[:, (nx - 1) // 2] = g1
|
|
253
257
|
d = self.data.copy()
|
|
254
258
|
d[np.isnan(d)] = 0
|
|
255
|
-
|
|
256
|
-
d = [[convolve(c, g, mode='same') for c in cc] for cc in d]
|
|
257
|
-
self.data = np.squeeze(d)
|
|
259
|
+
self.data = np.squeeze(convolve(to4dim(d), [[g]], mode='same'))
|
|
258
260
|
self.rotate(bpa)
|
|
259
261
|
self.beam[1] = self.beam[0]
|
|
260
262
|
self.beam[2] = 0
|
|
@@ -272,8 +274,10 @@ class AstroData():
|
|
|
272
274
|
self.data = RGIxy(self.y, self.x, self.data, yxnew, **kwargs)
|
|
273
275
|
if None not in self.beam:
|
|
274
276
|
bmaj, bmin, bpa = self.beam
|
|
275
|
-
a, b = np.linalg.multi_dot([Mfac(1/bmaj, 1/bmin),
|
|
276
|
-
|
|
277
|
+
a, b = np.linalg.multi_dot([Mfac(1 / bmaj, 1 / bmin),
|
|
278
|
+
Mrot(pa - bpa),
|
|
279
|
+
Mfac(1, ci),
|
|
280
|
+
Mrot(-pa)]).T
|
|
277
281
|
alpha = (np.dot(a, a) + np.dot(b, b)) / 2
|
|
278
282
|
beta = np.dot(a, b)
|
|
279
283
|
gamma = (np.dot(a, a) - np.dot(b, b)) / 2
|
|
@@ -406,6 +410,21 @@ class AstroData():
|
|
|
406
410
|
if len(excludepix) == 2:
|
|
407
411
|
self.data[(excludepix[0] < mask) * (mask < excludepix[1])] = np.nan
|
|
408
412
|
|
|
413
|
+
def _gfit_profile(self, prof: list, gaussfit: bool):
|
|
414
|
+
if not gaussfit:
|
|
415
|
+
return {}
|
|
416
|
+
|
|
417
|
+
gfitres = {}
|
|
418
|
+
nprof = len(prof)
|
|
419
|
+
res = [None] * nprof
|
|
420
|
+
for i in range(nprof):
|
|
421
|
+
res[i] = gaussfit1d(xdata=self.v, ydata=prof[i],
|
|
422
|
+
sigma=None, show=True,
|
|
423
|
+
nwalkersperdim=8)
|
|
424
|
+
gfitres['best'] = [a['popt'][:3] for a in res]
|
|
425
|
+
gfitres['error'] = [a['perr'][:3] for a in res]
|
|
426
|
+
return gfitres
|
|
427
|
+
|
|
409
428
|
def profile(self, coords: list[str] = [],
|
|
410
429
|
xlist: list[float] = [], ylist: list[float] = [],
|
|
411
430
|
ellipse: list[float, float, float] | None = None,
|
|
@@ -430,13 +449,12 @@ class AstroData():
|
|
|
430
449
|
print('Data must be 3D with the v, y, and x axes.')
|
|
431
450
|
return
|
|
432
451
|
|
|
433
|
-
v = self.v
|
|
434
452
|
data, xf, yf = filled2d(self.data, self.x, self.y, ninterp)
|
|
435
453
|
x, y = np.meshgrid(xf, yf)
|
|
436
454
|
if len(coords) > 0:
|
|
437
455
|
xlist, ylist = coord2xy(coords, self.center) * 3600.
|
|
438
456
|
nprof = len(xlist)
|
|
439
|
-
prof = np.empty((nprof, len(v)))
|
|
457
|
+
prof = np.empty((nprof, len(self.v)))
|
|
440
458
|
ellipse = ellipse or [[0, 0, 0]] * nprof
|
|
441
459
|
calc = np.sum if flux else np.mean
|
|
442
460
|
for i, (xc, yc, e) in enumerate(zip(xlist, ylist, ellipse)):
|
|
@@ -455,16 +473,8 @@ class AstroData():
|
|
|
455
473
|
else:
|
|
456
474
|
Omega = np.pi * self.beam[0] * self.beam[1] / 4. / np.log(2.)
|
|
457
475
|
prof *= np.abs(self.dx * self.dy) / Omega
|
|
458
|
-
gfitres =
|
|
459
|
-
|
|
460
|
-
res = [None] * nprof
|
|
461
|
-
for i in range(nprof):
|
|
462
|
-
res[i] = gaussfit1d(xdata=v, ydata=prof[i],
|
|
463
|
-
sigma=None, show=True,
|
|
464
|
-
nwalkersperdim=8)
|
|
465
|
-
gfitres['best'] = [a['popt'][:3] for a in res]
|
|
466
|
-
gfitres['error'] = [a['perr'][:3] for a in res]
|
|
467
|
-
return v, prof, gfitres
|
|
476
|
+
gfitres = self._gfit_profile(prof, gaussfit)
|
|
477
|
+
return self.v, prof, gfitres
|
|
468
478
|
|
|
469
479
|
def rotate(self, pa: float = 0, **kwargs):
|
|
470
480
|
"""Counter clockwise rotation with respect to the center.
|
|
@@ -515,6 +525,31 @@ class AstroData():
|
|
|
515
525
|
'bunit': self.bunit}
|
|
516
526
|
return d
|
|
517
527
|
|
|
528
|
+
def _put_header(self, h: dict, t: str, crpix: int, crval: float,
|
|
529
|
+
cdelt: float | None = None) -> None:
|
|
530
|
+
fhd = self.fitsheader
|
|
531
|
+
axis = {'x': 1, 'y': 2, 'v': 2 if self.pv else 3}[t]
|
|
532
|
+
u = f'CUNIT{axis}'
|
|
533
|
+
indeg = fhd is None or u not in fhd or isdeg(fhd[u])
|
|
534
|
+
h[f'NAXIS{axis}'] = len(getattr(self, t))
|
|
535
|
+
h[f'CRPIX{axis}'] = int(crpix)
|
|
536
|
+
h[f'CRVAL{axis}'] = float(crval)
|
|
537
|
+
if cdelt is None:
|
|
538
|
+
cdelt = getattr(self, f'd{t}') / (3600 if indeg else 1)
|
|
539
|
+
h[f'CDELT{axis}'] = float(cdelt)
|
|
540
|
+
|
|
541
|
+
def _get_cvdv_in_freq(self, ck: int) -> tuple[float, float]:
|
|
542
|
+
cv = self.v[ck]
|
|
543
|
+
dv = self.dv
|
|
544
|
+
if self.restfreq is None or self.restfreq == 0:
|
|
545
|
+
s = 'No valid restfreq. The velocity axis is saved as is.'
|
|
546
|
+
warnings.warn(s, UserWarning)
|
|
547
|
+
return cv, dv
|
|
548
|
+
|
|
549
|
+
cv = (1 - cv / cu.c_kms) * self.restfreq
|
|
550
|
+
dv = -dv / cu.c_kms * self.restfreq
|
|
551
|
+
return cv, dv
|
|
552
|
+
|
|
518
553
|
@_need_multipixels
|
|
519
554
|
def writetofits(self, fitsimage: str = 'out.fits',
|
|
520
555
|
header: dict = {}) -> None:
|
|
@@ -524,7 +559,6 @@ class AstroData():
|
|
|
524
559
|
fitsimage (str, optional): Output FITS file name. Defaults to 'out.fits'.
|
|
525
560
|
header (dict, optional): Header dictionary. Defaults to {}.
|
|
526
561
|
"""
|
|
527
|
-
fhd = self.fitsheader
|
|
528
562
|
h = {}
|
|
529
563
|
ci = nearest_index(self.x)
|
|
530
564
|
cx = 0
|
|
@@ -536,35 +570,13 @@ class AstroData():
|
|
|
536
570
|
xy = [self.x[ci] / 3600, self.y[cj] / 3600]
|
|
537
571
|
cx, cy = coord2xy(xy2coord(xy, self.center))
|
|
538
572
|
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
h['NAXIS1'] = len(self.x)
|
|
544
|
-
h['CRPIX1'] = int(ci + 1)
|
|
545
|
-
h['CRVAL1'] = float(cx)
|
|
546
|
-
h['CDELT1'] = float(self.dx / (3600 if indeg('1') else 1))
|
|
573
|
+
self._put_header(h, 'x', crpix=ci + 1, crval=cx)
|
|
574
|
+
if not self.pv:
|
|
575
|
+
self._put_header(h, 'y', crpix=cj + 1, crval=cy)
|
|
547
576
|
if self.dv is not None:
|
|
548
|
-
vaxis = '2' if self.pv else '3'
|
|
549
577
|
ck = nearest_index(self.v)
|
|
550
|
-
cv = self.
|
|
551
|
-
|
|
552
|
-
if self.restfreq is None or self.restfreq == 0:
|
|
553
|
-
s = 'No valid restfreq.' \
|
|
554
|
-
+ f' Axis {vaxis} is saved as is.'
|
|
555
|
-
warnings.warn(s, UserWarning)
|
|
556
|
-
else:
|
|
557
|
-
cv = (1 - cv / cu.c_kms) * self.restfreq
|
|
558
|
-
dv = -dv / cu.c_kms * self.restfreq
|
|
559
|
-
h[f'NAXIS{vaxis}'] = len(self.v)
|
|
560
|
-
h[f'CRPIX{vaxis}'] = int(ck + 1)
|
|
561
|
-
h[f'CRVAL{vaxis}'] = float(cv)
|
|
562
|
-
h[f'CDELT{vaxis}'] = float(dv)
|
|
563
|
-
if not self.pv:
|
|
564
|
-
h['NAXIS2'] = len(self.y)
|
|
565
|
-
h['CRPIX2'] = int(cj + 1)
|
|
566
|
-
h['CRVAL2'] = float(cy)
|
|
567
|
-
h['CDELT2'] = float(self.dy / (3600 if indeg('2') else 1))
|
|
578
|
+
cv, dv = self._get_cvdv_in_freq(ck)
|
|
579
|
+
self._put_header(h, 'v', crpix=ck + 1, crval=cv, cdelt=dv)
|
|
568
580
|
if None not in self.beam:
|
|
569
581
|
beam = self.beam_org if self.pv else self.beam
|
|
570
582
|
h['BMAJ'] = float(beam[0] / 3600)
|
|
@@ -590,9 +602,9 @@ def _get_gridsep(axis: np.ndarray | None):
|
|
|
590
602
|
return axis[1] - axis[0] if axis is not None and len(axis) > 1 else None
|
|
591
603
|
|
|
592
604
|
|
|
593
|
-
ASTRODATA_ARGS = [
|
|
594
|
-
|
|
595
|
-
|
|
605
|
+
ASTRODATA_ARGS = ['fitsimage', 'data', 'Tb', 'sigma', 'center', 'restfreq',
|
|
606
|
+
'cfactor', 'bunit', 'fitsimage_org', 'sigma_org',
|
|
607
|
+
'beam_org', 'fitsheader', 'pv', 'pvpa']
|
|
596
608
|
|
|
597
609
|
|
|
598
610
|
@dataclass
|
|
@@ -675,12 +687,12 @@ class AstroFrame():
|
|
|
675
687
|
Returns:
|
|
676
688
|
np.ndarray: absolute coordinates.
|
|
677
689
|
"""
|
|
678
|
-
onexy = np.shape(poslist) == (2,) and
|
|
690
|
+
onexy = np.shape(poslist) == (2,) and not isinstance(poslist[0], str)
|
|
679
691
|
if np.shape(poslist) == () or onexy:
|
|
680
692
|
poslist = [poslist]
|
|
681
693
|
x, y = [None] * len(poslist), [None] * len(poslist)
|
|
682
694
|
for i, p in enumerate(poslist):
|
|
683
|
-
if
|
|
695
|
+
if isinstance(p, str):
|
|
684
696
|
x[i], y[i] = coord2xy(p, self.center) * 3600.
|
|
685
697
|
else:
|
|
686
698
|
x[i], y[i] = rel2abs(*p, self.Xlim, self.Ylim)
|
|
@@ -688,12 +700,12 @@ class AstroFrame():
|
|
|
688
700
|
|
|
689
701
|
def _get_restfreq(self, header: dict):
|
|
690
702
|
"""Extract rest frequency from FITS header."""
|
|
691
|
-
if
|
|
692
|
-
return header[
|
|
693
|
-
if
|
|
694
|
-
return header[
|
|
695
|
-
if
|
|
696
|
-
return header[
|
|
703
|
+
if 'RESTFRQ' in header:
|
|
704
|
+
return header['RESTFRQ']
|
|
705
|
+
if 'RESTFREQ' in header:
|
|
706
|
+
return header['RESTFREQ']
|
|
707
|
+
if 'NAXIS3' in header and header['NAXIS3'] == 1 and not self.pv:
|
|
708
|
+
return header['CRVAL3']
|
|
697
709
|
return None
|
|
698
710
|
|
|
699
711
|
def _read_fitsimage(self, d: AstroData, i: int, grid: list) -> list:
|
|
@@ -715,7 +727,7 @@ class AstroFrame():
|
|
|
715
727
|
if fd.wcsrot:
|
|
716
728
|
d.center[i] = fd.get_center()
|
|
717
729
|
d.beam[i] = fd.get_beam(dist=self.dist)
|
|
718
|
-
d.bunit[i] = fd.get_header(
|
|
730
|
+
d.bunit[i] = fd.get_header('BUNIT')
|
|
719
731
|
return grid
|
|
720
732
|
|
|
721
733
|
def _shift_center(self, d: AstroData, i: int, grid: list) -> list:
|
|
@@ -758,7 +770,7 @@ class AstroFrame():
|
|
|
758
770
|
if self.pv:
|
|
759
771
|
d.v = d.y
|
|
760
772
|
for axis in ['x', 'y', 'v']:
|
|
761
|
-
setattr(d, f
|
|
773
|
+
setattr(d, f'd{axis}', _get_gridsep(getattr(d, axis)))
|
|
762
774
|
|
|
763
775
|
def _convert_to_Tb(self, d: AstroData, i: int):
|
|
764
776
|
"""Convert Jy/beam data to brightness temperature if requested."""
|
|
@@ -766,12 +778,12 @@ class AstroFrame():
|
|
|
766
778
|
return
|
|
767
779
|
|
|
768
780
|
dx = d.dy if self.swapxy else d.dx
|
|
769
|
-
header = {
|
|
770
|
-
|
|
771
|
-
|
|
781
|
+
header = {'CDELT1': dx / 3600,
|
|
782
|
+
'CUNIT1': 'deg',
|
|
783
|
+
'RESTFREQ': d.restfreq[i]}
|
|
772
784
|
if None not in d.beam[i]:
|
|
773
|
-
header[
|
|
774
|
-
header[
|
|
785
|
+
header['BMAJ'] = d.beam[i][0] / 3600 / self.dist
|
|
786
|
+
header['BMIN'] = d.beam[i][1] / 3600 / self.dist
|
|
775
787
|
factor = Jy2K(header=header)
|
|
776
788
|
d.data[i] = d.data[i] * factor
|
|
777
789
|
if d.sigma[i] is not None:
|
|
@@ -785,7 +797,7 @@ class AstroFrame():
|
|
|
785
797
|
bmaj, bmin, bpa = d.beam_org[i] = d.beam[i]
|
|
786
798
|
if d.pvpa[i] is None:
|
|
787
799
|
d.pvpa[i] = bpa
|
|
788
|
-
print(
|
|
800
|
+
print('pvpa is not specified. pvpa=bpa is assumed.')
|
|
789
801
|
angle = np.radians(bpa - d.pvpa[i])
|
|
790
802
|
beam_incut = 1 / np.hypot(np.cos(angle) / bmaj, np.sin(angle) / bmin)
|
|
791
803
|
d.beam[i] = np.array([np.abs(d.dv), beam_incut, 0])
|
|
@@ -827,5 +839,5 @@ class AstroFrame():
|
|
|
827
839
|
d.beam = _as_list(d.beam, d.n, isbeam=True)
|
|
828
840
|
for i in range(d.n):
|
|
829
841
|
self._read_one(d, i)
|
|
830
|
-
for name in ASTRODATA_ARGS + [
|
|
842
|
+
for name in ASTRODATA_ARGS + ['beam']:
|
|
831
843
|
setattr(d, name, _scalar_if_single(getattr(d, name), d.n))
|
|
@@ -3,7 +3,7 @@ from astropy import units
|
|
|
3
3
|
from astropy.coordinates import FK4, FK5, SkyCoord
|
|
4
4
|
|
|
5
5
|
|
|
6
|
-
def _updateframe(frame: str) -> str:
|
|
6
|
+
def _updateframe(frame: str | None) -> str:
|
|
7
7
|
"""Internal function to str frame to astropy frame.
|
|
8
8
|
|
|
9
9
|
Args:
|
|
@@ -18,7 +18,7 @@ def _updateframe(frame: str) -> str:
|
|
|
18
18
|
a = FK5(equinox='J2000')
|
|
19
19
|
elif 'B1950' in frame or 'FK4' in frame:
|
|
20
20
|
a = FK4(equinox='B1950')
|
|
21
|
-
elif
|
|
21
|
+
elif isinstance(frame, str):
|
|
22
22
|
print(f'Unknown frame ({frame}) was found. Use ICRS instead.')
|
|
23
23
|
a = 'icrs'
|
|
24
24
|
else:
|
|
@@ -26,7 +26,7 @@ def _updateframe(frame: str) -> str:
|
|
|
26
26
|
return a
|
|
27
27
|
|
|
28
28
|
|
|
29
|
-
def _getframe(coord: str) -> tuple:
|
|
29
|
+
def _getframe(coord: str | list) -> tuple:
|
|
30
30
|
"""Internal function to pick up the frame name from the coordinates. When coord is a list, frame and framename are picked up from the first element.
|
|
31
31
|
|
|
32
32
|
Args:
|
|
@@ -43,7 +43,7 @@ def _getframe(coord: str) -> tuple:
|
|
|
43
43
|
framename = c[0] if hasframe else None
|
|
44
44
|
return hmsdms, frame, framename
|
|
45
45
|
|
|
46
|
-
if
|
|
46
|
+
if isinstance(coord, str):
|
|
47
47
|
return getframe_single(coord)
|
|
48
48
|
else:
|
|
49
49
|
outlist = [getframe_single(c) for c in coord]
|
|
@@ -108,7 +108,7 @@ def xy2coord(xy: list, coordorg: str = '00h00m00s 00d00m00s',
|
|
|
108
108
|
coords = coords.transform_to(frame=frame)
|
|
109
109
|
coords = coords.to_string('hmsdms')
|
|
110
110
|
if framename is not None:
|
|
111
|
-
if
|
|
111
|
+
if isinstance(coords, str):
|
|
112
112
|
coords = f'{framename} {coords}'
|
|
113
113
|
else:
|
|
114
114
|
coords = np.array([f'{framename} {s}' for s in coords])
|
|
@@ -241,23 +241,23 @@ class FitsData:
|
|
|
241
241
|
h = self.header
|
|
242
242
|
cxy = (0, 0)
|
|
243
243
|
if center is not None and not self.wcsrot:
|
|
244
|
-
coordorg = xy2coord([h[
|
|
245
|
-
if (radesys := h.get(
|
|
246
|
-
coordorg = f
|
|
244
|
+
coordorg = xy2coord([h['CRVAL1'], h['CRVAL2']])
|
|
245
|
+
if (radesys := h.get('RADESYS')) is not None:
|
|
246
|
+
coordorg = f'{radesys} {coordorg}'
|
|
247
247
|
cxy = coord2xy(center, coordorg)
|
|
248
|
-
slabel = [
|
|
248
|
+
slabel = ['x', 'y']
|
|
249
249
|
|
|
250
250
|
def wrapper(i: int):
|
|
251
251
|
def gen_s(s_in: np.ndarray | None) -> None:
|
|
252
|
-
if h.get(f
|
|
252
|
+
if h.get(f'NAXIS{i+1}') is None or s_in is None:
|
|
253
253
|
s, ds = None, None
|
|
254
254
|
else:
|
|
255
255
|
s = (s_in - cxy[i]) * dist
|
|
256
|
-
if isdeg(h[f
|
|
256
|
+
if isdeg(h[f'CUNIT{i+1}']):
|
|
257
257
|
s *= 3600.
|
|
258
258
|
ds = None if len(s) == 0 else s[1] - s[0]
|
|
259
|
-
setattr(self, f
|
|
260
|
-
setattr(self, f
|
|
259
|
+
setattr(self, f'{slabel[i]}', s)
|
|
260
|
+
setattr(self, f'd{slabel[i]}', ds)
|
|
261
261
|
return gen_s
|
|
262
262
|
|
|
263
263
|
return wrapper(0), wrapper(1)
|
|
@@ -266,29 +266,29 @@ class FitsData:
|
|
|
266
266
|
h = self.header
|
|
267
267
|
|
|
268
268
|
def gen_v(v_in: np.ndarray) -> None:
|
|
269
|
-
vaxis =
|
|
270
|
-
if h.get(f
|
|
269
|
+
vaxis = '2' if pv else '3'
|
|
270
|
+
if h.get(f'NAXIS{vaxis}') is None or v_in is None:
|
|
271
271
|
self.v, self.dv = None, None
|
|
272
272
|
return
|
|
273
273
|
|
|
274
274
|
if restfreq is None:
|
|
275
275
|
freq = np.mean(v_in)
|
|
276
|
-
print(
|
|
276
|
+
print('restfreq is assumed to be the center.')
|
|
277
277
|
else:
|
|
278
278
|
freq = restfreq
|
|
279
|
-
v = v_in + h[f
|
|
279
|
+
v = v_in + h[f'CRVAL{vaxis}']
|
|
280
280
|
key = f'CUNIT{vaxis}'
|
|
281
281
|
cunitv = h[key].strip()
|
|
282
282
|
match cunitv:
|
|
283
|
-
case
|
|
283
|
+
case 'Hz' | 'HZ':
|
|
284
284
|
if freq == 0:
|
|
285
|
-
print(
|
|
285
|
+
print('v is read as is, because restfreq=0.')
|
|
286
286
|
else:
|
|
287
287
|
v = (1 - v / freq) * cu.c_kms - vsys
|
|
288
|
-
case
|
|
288
|
+
case 'm/s' | 'M/S':
|
|
289
289
|
print(f'{key}={cunitv} found.')
|
|
290
290
|
v = v * 1e-3 - vsys
|
|
291
|
-
case 'km/s' |
|
|
291
|
+
case 'km/s' | 'KM/S':
|
|
292
292
|
print(f'{key}={cunitv} found.')
|
|
293
293
|
v = v - vsys
|
|
294
294
|
case _:
|
|
@@ -301,11 +301,11 @@ class FitsData:
|
|
|
301
301
|
|
|
302
302
|
def _get_array(self, i: int) -> np.ndarray:
|
|
303
303
|
h = self.header
|
|
304
|
-
n = h.get(f
|
|
304
|
+
n = h.get(f'NAXIS{i:d}')
|
|
305
305
|
if n is None:
|
|
306
306
|
return None
|
|
307
307
|
|
|
308
|
-
s = (np.arange(n) - h[f
|
|
308
|
+
s = (np.arange(n) - h[f'CRPIX{i:d}'] + 1) * h[f'CDELT{i:d}']
|
|
309
309
|
return s
|
|
310
310
|
|
|
311
311
|
def gen_grid(self, center: str | None = None, dist: float = 1.,
|
|
@@ -343,7 +343,7 @@ class FitsData:
|
|
|
343
343
|
Returns:
|
|
344
344
|
tuple: (x, y, v).
|
|
345
345
|
"""
|
|
346
|
-
if not np.all([hasattr(self, s) for s in [
|
|
346
|
+
if not np.all([hasattr(self, s) for s in ['x', 'y', 'v']]):
|
|
347
347
|
self.gen_grid(**kwargs)
|
|
348
348
|
return self.x, self.y, self.v
|
|
349
349
|
|
|
@@ -376,7 +376,7 @@ def fits2data(fitsimage: str, Tb: bool = False, log: bool = False,
|
|
|
376
376
|
restfreq: float | None = None, center: str | None = None,
|
|
377
377
|
vsys: float = 0., pv: bool = False, **kwargs
|
|
378
378
|
) -> tuple[np.ndarray, tuple[np.ndarray, np.ndarray, np.ndarray],
|
|
379
|
-
|
|
379
|
+
np.ndarray, str, float]:
|
|
380
380
|
"""Extract data from a fits file. kwargs are arguments of FitsData.trim().
|
|
381
381
|
|
|
382
382
|
Args:
|