plotastrodata 1.9.7__tar.gz → 1.9.9__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.7/plotastrodata.egg-info → plotastrodata-1.9.9}/PKG-INFO +1 -1
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/__init__.py +1 -1
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/analysis_utils.py +10 -12
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/fits_utils.py +144 -139
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/plot_utils.py +127 -108
- {plotastrodata-1.9.7 → plotastrodata-1.9.9/plotastrodata.egg-info}/PKG-INFO +1 -1
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/LICENSE +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/MANIFEST.in +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/README.md +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/const_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/coord_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/ext_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/fft_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/fitting_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/los_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/matrix_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/noise_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata/other_utils.py +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata.egg-info/SOURCES.txt +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata.egg-info/dependency_links.txt +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata.egg-info/not-zip-safe +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata.egg-info/requires.txt +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/plotastrodata.egg-info/top_level.txt +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/setup.cfg +0 -0
- {plotastrodata-1.9.7 → plotastrodata-1.9.9}/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.9
|
|
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
|
|
@@ -688,12 +688,12 @@ class AstroFrame():
|
|
|
688
688
|
|
|
689
689
|
def _get_restfreq(self, header: dict):
|
|
690
690
|
"""Extract rest frequency from FITS header."""
|
|
691
|
-
if "NAXIS3" in header and header["NAXIS3"] == 1 and not self.pv:
|
|
692
|
-
return header["CRVAL3"]
|
|
693
691
|
if "RESTFRQ" in header:
|
|
694
692
|
return header["RESTFRQ"]
|
|
695
693
|
if "RESTFREQ" in header:
|
|
696
694
|
return header["RESTFREQ"]
|
|
695
|
+
if "NAXIS3" in header and header["NAXIS3"] == 1 and not self.pv:
|
|
696
|
+
return header["CRVAL3"]
|
|
697
697
|
return None
|
|
698
698
|
|
|
699
699
|
def _read_fitsimage(self, d: AstroData, i: int, grid: list) -> list:
|
|
@@ -718,20 +718,16 @@ class AstroFrame():
|
|
|
718
718
|
d.bunit[i] = fd.get_header("BUNIT")
|
|
719
719
|
return grid
|
|
720
720
|
|
|
721
|
-
def _no_newcenter(self, center: str | None):
|
|
722
|
-
return (self.pv
|
|
723
|
-
or self.center is None
|
|
724
|
-
or center is None
|
|
725
|
-
or center == self.center)
|
|
726
|
-
|
|
727
721
|
def _shift_center(self, d: AstroData, i: int, grid: list) -> list:
|
|
728
|
-
|
|
722
|
+
corg = d.center[i]
|
|
723
|
+
cnew = self.center
|
|
724
|
+
if self.pv or cnew is None or corg is None or corg == cnew:
|
|
729
725
|
return grid
|
|
730
726
|
|
|
731
|
-
cx, cy = coord2xy(
|
|
727
|
+
cx, cy = coord2xy(corg, cnew) * 3600
|
|
732
728
|
grid[0] = grid[0] + cx # Don't use += cx.
|
|
733
729
|
grid[1] = grid[1] + cy # Don't use += cy.
|
|
734
|
-
d.center[i] =
|
|
730
|
+
d.center[i] = cnew
|
|
735
731
|
return grid
|
|
736
732
|
|
|
737
733
|
def _ascending_v(self, d: AstroData, i: int, v: np.ndarray | None):
|
|
@@ -756,9 +752,11 @@ class AstroFrame():
|
|
|
756
752
|
self._ascending_v(d, i, v=grid[2])
|
|
757
753
|
grid = [grid[0], d.v] if self.pv else [grid[0], grid[1]]
|
|
758
754
|
if self.swapxy:
|
|
759
|
-
grid
|
|
755
|
+
grid.reverse()
|
|
760
756
|
d.data[i] = np.moveaxis(d.data[i], 1, 0)
|
|
761
757
|
self._xyskip(d, i, x=grid[0], y=grid[1])
|
|
758
|
+
if self.pv:
|
|
759
|
+
d.v = d.y
|
|
762
760
|
for axis in ['x', 'y', 'v']:
|
|
763
761
|
setattr(d, f"d{axis}", _get_gridsep(getattr(d, axis)))
|
|
764
762
|
|
|
@@ -177,6 +177,137 @@ class FitsData:
|
|
|
177
177
|
self.gen_data(**kwargs)
|
|
178
178
|
return self.data
|
|
179
179
|
|
|
180
|
+
def _read_cd(self):
|
|
181
|
+
h = self.header
|
|
182
|
+
cdij = ['CD1_1', 'CD1_2', 'CD2_1', 'CD2_2']
|
|
183
|
+
if not np.all([k in list(h.keys()) for k in cdij]):
|
|
184
|
+
self.wcsrot = False
|
|
185
|
+
self.Mcd = None
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
self.wcsrot = True
|
|
189
|
+
cd11, cd12, cd21, cd22 = [h[k] for k in cdij]
|
|
190
|
+
self.Mcd = [[cd11, cd12], [cd21, cd22]]
|
|
191
|
+
if cd21 == 0:
|
|
192
|
+
rho_a = 0
|
|
193
|
+
else:
|
|
194
|
+
rho_a = np.arctan2(np.abs(cd21), np.sign(cd21) * cd11)
|
|
195
|
+
if cd12 == 0:
|
|
196
|
+
rho_b = 0
|
|
197
|
+
else:
|
|
198
|
+
rho_b = np.arctan2(np.abs(cd12), -np.sign(cd12) * cd22)
|
|
199
|
+
if (drho := np.abs(np.degrees(rho_a - rho_b))) > 1.0:
|
|
200
|
+
print('Angles from (CD21, CD11) and (CD12, CD22)'
|
|
201
|
+
+ f' are different by {drho:.2} degrees.')
|
|
202
|
+
crota2 = (rho_a + rho_b) / 2.
|
|
203
|
+
sin_rho = np.sin(crota2)
|
|
204
|
+
cos_rho = np.cos(crota2)
|
|
205
|
+
cdelt1 = cd11 * cos_rho + cd21 * sin_rho
|
|
206
|
+
cdelt2 = -cd12 * sin_rho + cd22 * cos_rho
|
|
207
|
+
crota2 = np.degrees(crota2)
|
|
208
|
+
h['CDELT1'] = cdelt1
|
|
209
|
+
h['CDELT2'] = cdelt2
|
|
210
|
+
for k in cdij:
|
|
211
|
+
del h[k]
|
|
212
|
+
print(f'WCS rotation was found (CROTA2 = {crota2:f} deg).')
|
|
213
|
+
|
|
214
|
+
def _rotate_cd(self):
|
|
215
|
+
h = self.header
|
|
216
|
+
data = self.get_data()
|
|
217
|
+
ic = len(self.x) // 2
|
|
218
|
+
jc = len(self.y) // 2
|
|
219
|
+
h['CRPIX1'] = ic + 1
|
|
220
|
+
h['CRPIX2'] = jc + 1
|
|
221
|
+
xc = self.x[ic] / self.dx
|
|
222
|
+
yc = self.y[jc] / self.dy
|
|
223
|
+
xc, yc = dot2d(self.Mcd, [xc, yc])
|
|
224
|
+
newcenter = xy2coord([xc, yc], coordorg=self.get_center())
|
|
225
|
+
xc, yc = coord2xy(coords=newcenter)
|
|
226
|
+
h['CRVAL1'] = xc
|
|
227
|
+
h['CRVAL2'] = yc
|
|
228
|
+
self.x = self.x - self.x[ic]
|
|
229
|
+
self.y = self.y - self.y[jc]
|
|
230
|
+
x = self.x / (3600 if isdeg(h['CUNIT1']) else 1)
|
|
231
|
+
y = self.y / (3600 if isdeg(h['CUNIT2']) else 1)
|
|
232
|
+
X, Y = np.meshgrid(x, y)
|
|
233
|
+
Mcdinv = np.linalg.inv(self.Mcd)
|
|
234
|
+
xnew, ynew = dot2d(Mcdinv, [X, Y])
|
|
235
|
+
datanew = RGIxy(self.y / self.dy, self.x / self.dx,
|
|
236
|
+
data, (ynew, xnew))
|
|
237
|
+
self.data = datanew
|
|
238
|
+
print('Data values were interpolated for WCS rotation.')
|
|
239
|
+
|
|
240
|
+
def _get_genx_geny(self, center: str, dist: float):
|
|
241
|
+
h = self.header
|
|
242
|
+
cxy = (0, 0)
|
|
243
|
+
if center is not None and not self.wcsrot:
|
|
244
|
+
coordorg = xy2coord([h["CRVAL1"], h["CRVAL2"]])
|
|
245
|
+
if (radesys := h.get("RADESYS")) is not None:
|
|
246
|
+
coordorg = f"{radesys} {coordorg}"
|
|
247
|
+
cxy = coord2xy(center, coordorg)
|
|
248
|
+
slabel = ["x", "y"]
|
|
249
|
+
|
|
250
|
+
def wrapper(i: int):
|
|
251
|
+
def gen_s(s_in: np.ndarray | None) -> None:
|
|
252
|
+
if h.get(f"NAXIS{i+1}") is None or s_in is None:
|
|
253
|
+
s, ds = None, None
|
|
254
|
+
else:
|
|
255
|
+
s = (s_in - cxy[i]) * dist
|
|
256
|
+
if isdeg(h[f"CUNIT{i+1}"]):
|
|
257
|
+
s *= 3600.
|
|
258
|
+
ds = None if len(s) == 0 else s[1] - s[0]
|
|
259
|
+
setattr(self, f"{slabel[i]}", s)
|
|
260
|
+
setattr(self, f"d{slabel[i]}", ds)
|
|
261
|
+
return gen_s
|
|
262
|
+
|
|
263
|
+
return wrapper(0), wrapper(1)
|
|
264
|
+
|
|
265
|
+
def _get_genv(self, restfreq: float | None, vsys: float, pv: bool):
|
|
266
|
+
h = self.header
|
|
267
|
+
|
|
268
|
+
def gen_v(v_in: np.ndarray) -> None:
|
|
269
|
+
vaxis = "2" if pv else "3"
|
|
270
|
+
if h.get(f"NAXIS{vaxis}") is None or v_in is None:
|
|
271
|
+
self.v, self.dv = None, None
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if restfreq is None:
|
|
275
|
+
freq = np.mean(v_in)
|
|
276
|
+
print("restfreq is assumed to be the center.")
|
|
277
|
+
else:
|
|
278
|
+
freq = restfreq
|
|
279
|
+
v = v_in + h[f"CRVAL{vaxis}"]
|
|
280
|
+
key = f'CUNIT{vaxis}'
|
|
281
|
+
cunitv = h[key].strip()
|
|
282
|
+
match cunitv:
|
|
283
|
+
case "Hz" | "HZ":
|
|
284
|
+
if freq == 0:
|
|
285
|
+
print("v is read as is, because restfreq=0.")
|
|
286
|
+
else:
|
|
287
|
+
v = (1 - v / freq) * cu.c_kms - vsys
|
|
288
|
+
case "m/s" | "M/S":
|
|
289
|
+
print(f'{key}={cunitv} found.')
|
|
290
|
+
v = v * 1e-3 - vsys
|
|
291
|
+
case 'km/s' | "KM/S":
|
|
292
|
+
print(f'{key}={cunitv} found.')
|
|
293
|
+
v = v - vsys
|
|
294
|
+
case _:
|
|
295
|
+
print(f'Unknown CUNIT3 {cunitv} found.'
|
|
296
|
+
+ ' v is read as is.')
|
|
297
|
+
dv = None if len(v) == 0 else v[1] - v[0]
|
|
298
|
+
self.v, self.dv = v, dv
|
|
299
|
+
|
|
300
|
+
return gen_v
|
|
301
|
+
|
|
302
|
+
def _get_array(self, i: int) -> np.ndarray:
|
|
303
|
+
h = self.header
|
|
304
|
+
n = h.get(f"NAXIS{i:d}")
|
|
305
|
+
if n is None:
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
s = (np.arange(n) - h[f"CRPIX{i:d}"] + 1) * h[f"CDELT{i:d}"]
|
|
309
|
+
return s
|
|
310
|
+
|
|
180
311
|
def gen_grid(self, center: str | None = None, dist: float = 1.,
|
|
181
312
|
restfreq: float | None = None, vsys: float = 0.,
|
|
182
313
|
pv: bool = False) -> None:
|
|
@@ -191,146 +322,20 @@ class FitsData:
|
|
|
191
322
|
"""
|
|
192
323
|
h = self.get_header()
|
|
193
324
|
# WCS rotation (Calabretta & Greisen 2002, Astronomy & Astrophysics, 395, 1077)
|
|
194
|
-
self.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
rho_a = np.arctan2(np.abs(cd21), np.sign(cd21) * cd11)
|
|
203
|
-
if cd12 == 0:
|
|
204
|
-
rho_b = 0
|
|
205
|
-
else:
|
|
206
|
-
rho_b = np.arctan2(np.abs(cd12), -np.sign(cd12) * cd22)
|
|
207
|
-
if (drho := np.abs(np.degrees(rho_a - rho_b))) > 1.0:
|
|
208
|
-
print('Angles from (CD21, CD11) and (CD12, CD22)'
|
|
209
|
-
+ f' are different by {drho:.2} degrees.')
|
|
210
|
-
crota2 = (rho_a + rho_b) / 2.
|
|
211
|
-
sin_rho = np.sin(crota2)
|
|
212
|
-
cos_rho = np.cos(crota2)
|
|
213
|
-
cdelt1 = cd11 * cos_rho + cd21 * sin_rho
|
|
214
|
-
cdelt2 = -cd12 * sin_rho + cd22 * cos_rho
|
|
215
|
-
crota2 = np.degrees(crota2)
|
|
216
|
-
h['CDELT1'] = cdelt1
|
|
217
|
-
h['CDELT2'] = cdelt2
|
|
218
|
-
del h['CD1_1']
|
|
219
|
-
del h['CD1_2']
|
|
220
|
-
del h['CD2_1']
|
|
221
|
-
del h['CD2_2']
|
|
222
|
-
print(f'WCS rotation was found (CROTA2 = {crota2:f} deg).')
|
|
223
|
-
# spatial center
|
|
224
|
-
if center is None or self.wcsrot:
|
|
225
|
-
cx, cy = 0, 0
|
|
325
|
+
self._read_cd()
|
|
326
|
+
gen_x, gen_y = self._get_genx_geny(center, dist)
|
|
327
|
+
restfreq = restfreq or h.get('RESTFRQ') or h.get('RESTFREQ')
|
|
328
|
+
gen_v = self._get_genv(restfreq, vsys, pv)
|
|
329
|
+
if pv:
|
|
330
|
+
gen_x(self._get_array(1))
|
|
331
|
+
gen_v(self._get_array(2))
|
|
332
|
+
self.y, self.dy = None, None
|
|
226
333
|
else:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
c0 = f'{radesys} {c0}'
|
|
231
|
-
cx, cy = coord2xy(center, c0)
|
|
232
|
-
# rest frequency
|
|
233
|
-
if restfreq is None:
|
|
234
|
-
if 'RESTFRQ' in h:
|
|
235
|
-
restfreq = h['RESTFRQ']
|
|
236
|
-
if 'RESTFREQ' in h:
|
|
237
|
-
restfreq = h['RESTFREQ']
|
|
238
|
-
self.x, self.y, self.v = None, None, None
|
|
239
|
-
self.dx, self.dy, self.dv = None, None, None
|
|
240
|
-
|
|
241
|
-
def get_list(i: int, crval=False) -> np.ndarray:
|
|
242
|
-
s = np.arange(h[f'NAXIS{i:d}'])
|
|
243
|
-
s = (s - h[f'CRPIX{i:d}'] + 1) * h[f'CDELT{i:d}']
|
|
244
|
-
if crval:
|
|
245
|
-
s = s + h[f'CRVAL{i:d}']
|
|
246
|
-
return s
|
|
247
|
-
|
|
248
|
-
def gen_x(s_in: np.ndarray) -> None:
|
|
249
|
-
s = (s_in - cx) * dist
|
|
250
|
-
if isdeg(h['CUNIT1']):
|
|
251
|
-
s *= 3600.
|
|
252
|
-
self.x, self.dx = s, s[1] - s[0]
|
|
253
|
-
|
|
254
|
-
def gen_y(s_in: np.ndarray) -> None:
|
|
255
|
-
s = (s_in - cy) * dist
|
|
256
|
-
if isdeg(h['CUNIT2']):
|
|
257
|
-
s *= 3600.
|
|
258
|
-
self.y, self.dy = s, s[1] - s[0]
|
|
259
|
-
|
|
260
|
-
def gen_v(s_in: np.ndarray) -> None:
|
|
261
|
-
if restfreq is None:
|
|
262
|
-
freq = np.mean(s_in)
|
|
263
|
-
print('restfreq is assumed to be the center.')
|
|
264
|
-
else:
|
|
265
|
-
freq = restfreq
|
|
266
|
-
|
|
267
|
-
vaxis = '2' if pv else '3'
|
|
268
|
-
key = f'CUNIT{vaxis}'
|
|
269
|
-
cunitv = h[key]
|
|
270
|
-
match cunitv.strip():
|
|
271
|
-
case 'Hz':
|
|
272
|
-
if freq == 0:
|
|
273
|
-
print('v is frequency because restfreq=0.')
|
|
274
|
-
s = s_in * 1
|
|
275
|
-
else:
|
|
276
|
-
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
277
|
-
case 'HZ':
|
|
278
|
-
if freq == 0:
|
|
279
|
-
print('v is frequency because restfreq=0.')
|
|
280
|
-
s = s_in * 1
|
|
281
|
-
else:
|
|
282
|
-
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
283
|
-
case 'm/s':
|
|
284
|
-
print(f'{key}=\'m/s\' found.')
|
|
285
|
-
s = s_in * 1e-3 - vsys
|
|
286
|
-
case 'M/S':
|
|
287
|
-
print(f'{key}=\'M/S\' found.')
|
|
288
|
-
s = s_in * 1e-3 - vsys
|
|
289
|
-
case 'km/s':
|
|
290
|
-
print(f'{key}=\'km/s\' found.')
|
|
291
|
-
s = s_in - vsys
|
|
292
|
-
case 'KM/S':
|
|
293
|
-
print(f'{key}=\'KM/S\' found.')
|
|
294
|
-
s = s_in - vsys
|
|
295
|
-
case _:
|
|
296
|
-
print(f'Unknown CUNIT3 {cunitv} found.'
|
|
297
|
-
+ ' v is read as is.')
|
|
298
|
-
s = s_in - vsys
|
|
299
|
-
|
|
300
|
-
self.v, self.dv = s, s[1] - s[0]
|
|
301
|
-
|
|
302
|
-
if h['NAXIS'] > 0 and h['NAXIS1'] > 1:
|
|
303
|
-
gen_x(get_list(1))
|
|
304
|
-
if h['NAXIS'] > 1 and h['NAXIS2'] > 1:
|
|
305
|
-
gen_v(get_list(2, True)) if pv else gen_y(get_list(2))
|
|
306
|
-
if h['NAXIS'] > 2 and h['NAXIS3'] > 1:
|
|
307
|
-
gen_v(get_list(3, True))
|
|
308
|
-
|
|
334
|
+
gen_x(self._get_array(1))
|
|
335
|
+
gen_y(self._get_array(2))
|
|
336
|
+
gen_v(self._get_array(3))
|
|
309
337
|
if self.wcsrot:
|
|
310
|
-
|
|
311
|
-
self.header['CRPIX1'] = ic = len(self.x) // 2
|
|
312
|
-
self.header['CRPIX2'] = jc = len(self.y) // 2
|
|
313
|
-
xc = self.x[ic] / self.dx
|
|
314
|
-
yc = self.y[jc] / self.dy
|
|
315
|
-
Mcd = [[cd11, cd12], [cd21, cd22]]
|
|
316
|
-
xc, yc = dot2d(Mcd, [xc, yc])
|
|
317
|
-
newcenter = xy2coord(xy=[xc, yc],
|
|
318
|
-
coordorg=self.get_center())
|
|
319
|
-
xc, yc = coord2xy(coords=newcenter,
|
|
320
|
-
coordorg='00h00m00s 00d00m00s')
|
|
321
|
-
self.header['CRVAL1'] = xc
|
|
322
|
-
self.header['CRVAL2'] = yc
|
|
323
|
-
self.x = self.x - self.x[ic]
|
|
324
|
-
self.y = self.y - self.y[jc]
|
|
325
|
-
x = self.x / (3600 if isdeg(h['CUNIT1']) else 1)
|
|
326
|
-
y = self.y / (3600 if isdeg(h['CUNIT2']) else 1)
|
|
327
|
-
X, Y = np.meshgrid(x, y)
|
|
328
|
-
cdinv = np.linalg.inv([[cd11, cd12], [cd21, cd22]])
|
|
329
|
-
xnew, ynew = dot2d(cdinv, [X, Y])
|
|
330
|
-
datanew = RGIxy(self.y / self.dy, self.x / self.dx,
|
|
331
|
-
data, (ynew, xnew))
|
|
332
|
-
self.data = datanew
|
|
333
|
-
print('Data values were interpolated for WCS rotation.')
|
|
338
|
+
self._rotate_cd()
|
|
334
339
|
|
|
335
340
|
def get_grid(self, **kwargs) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
336
341
|
"""Output the grids, [x, y, v]. This method can take the arguments of gen_grid().
|
|
@@ -338,7 +343,7 @@ class FitsData:
|
|
|
338
343
|
Returns:
|
|
339
344
|
tuple: (x, y, v).
|
|
340
345
|
"""
|
|
341
|
-
if not hasattr(self,
|
|
346
|
+
if not np.all([hasattr(self, s) for s in ["x", "y", "v"]]):
|
|
342
347
|
self.gen_grid(**kwargs)
|
|
343
348
|
return self.x, self.y, self.v
|
|
344
349
|
|
|
@@ -8,6 +8,7 @@ from typing import TypeVar
|
|
|
8
8
|
from plotastrodata.analysis_utils import AstroData, AstroFrame
|
|
9
9
|
from plotastrodata.coord_utils import (coord2xy, xy2coord,
|
|
10
10
|
get_hmdm, get_min, get_sec)
|
|
11
|
+
from plotastrodata.fitting_utils import gaussian1d
|
|
11
12
|
from plotastrodata.noise_utils import estimate_rms
|
|
12
13
|
from plotastrodata.other_utils import (close_figure, listing,
|
|
13
14
|
reform_grid, reform_data)
|
|
@@ -149,7 +150,7 @@ def _get_gridwidth(mode: str, rmax: float, cos_dec: float
|
|
|
149
150
|
def _get_v(p, v: np.ndarray | None = None,
|
|
150
151
|
restfreq: float | None = None,
|
|
151
152
|
vskip: int = 1) -> np.ndarray:
|
|
152
|
-
if p.fitsimage is not None:
|
|
153
|
+
if p.fitsimage is not None and v is None:
|
|
153
154
|
p.read(d := AstroData(fitsimage=p.fitsimage,
|
|
154
155
|
restfreq=restfreq, sigma=None))
|
|
155
156
|
v = d.v
|
|
@@ -1199,6 +1200,55 @@ class PlotAstroData(AstroFrame):
|
|
|
1199
1200
|
return fig, self.ax[0]
|
|
1200
1201
|
|
|
1201
1202
|
|
|
1203
|
+
def _get_ylabel_profile(_kw: dict, Tb: bool, flux: bool, bunit: str
|
|
1204
|
+
) -> str:
|
|
1205
|
+
if "ylabel" in _kw:
|
|
1206
|
+
return _kw["ylabel"]
|
|
1207
|
+
if Tb:
|
|
1208
|
+
return r"$T_b$ (K)"
|
|
1209
|
+
if flux:
|
|
1210
|
+
return "Flux (Jy)"
|
|
1211
|
+
return bunit
|
|
1212
|
+
|
|
1213
|
+
|
|
1214
|
+
def _prep_plotprofile(width: int, coords: list | str,
|
|
1215
|
+
xlist: list, ylist: list, ellipse: list,
|
|
1216
|
+
ninterp: int, flux: bool, gaussfit: bool,
|
|
1217
|
+
_kw: dict) -> tuple:
|
|
1218
|
+
if isinstance(coords, str):
|
|
1219
|
+
coords = [coords]
|
|
1220
|
+
Tb = _kw.get("Tb", False)
|
|
1221
|
+
f = kwargs2instance(AstroFrame, _kw)
|
|
1222
|
+
d = kwargs2instance(AstroData, _kw)
|
|
1223
|
+
f.read(d)
|
|
1224
|
+
d.binning([width, 1, 1])
|
|
1225
|
+
v, prof, gfitres = d.profile(coords=coords, xlist=xlist, ylist=ylist,
|
|
1226
|
+
ellipse=ellipse, ninterp=ninterp,
|
|
1227
|
+
flux=flux, gaussfit=gaussfit)
|
|
1228
|
+
ylabel = _get_ylabel_profile(_kw, Tb, flux, d.bunit)
|
|
1229
|
+
if isinstance(ylabel, str):
|
|
1230
|
+
ylabel = [ylabel] * len(prof)
|
|
1231
|
+
_kw.setdefault("xlim", [v.min(), v.max()])
|
|
1232
|
+
pa2 = kwargs2instance(PlotAxes2D, _kw)
|
|
1233
|
+
return v, prof, gfitres, pa2, ylabel
|
|
1234
|
+
|
|
1235
|
+
|
|
1236
|
+
def _set_figax_plotprofile(fig, ax, nrows: int, ncols: int,
|
|
1237
|
+
nprof: int) -> tuple:
|
|
1238
|
+
if ncols == 1:
|
|
1239
|
+
nrows = nprof
|
|
1240
|
+
if fig is None:
|
|
1241
|
+
fig = plt.figure(figsize=(6 * ncols, 3 * nrows))
|
|
1242
|
+
if nprof > 1 and ax is not None:
|
|
1243
|
+
print("External ax is supported only when len(coords)=1.")
|
|
1244
|
+
ax = None
|
|
1245
|
+
ax = np.empty(nprof, dtype=object) if ax is None else [ax]
|
|
1246
|
+
for i in range(nprof):
|
|
1247
|
+
sharex = None if i < nrows - 1 else ax[i - 1]
|
|
1248
|
+
ax[i] = fig.add_subplot(nrows, ncols, i + 1, sharex=sharex)
|
|
1249
|
+
return fig, ax
|
|
1250
|
+
|
|
1251
|
+
|
|
1202
1252
|
def plotprofile(coords: list[str] | str = [],
|
|
1203
1253
|
xlist: list[float] = [], ylist: list[float] = [],
|
|
1204
1254
|
ellipse: list[float, float, float] | None = None,
|
|
@@ -1208,11 +1258,9 @@ def plotprofile(coords: list[str] | str = [],
|
|
|
1208
1258
|
title: list[str] | None = None,
|
|
1209
1259
|
text: list[str] | None = None,
|
|
1210
1260
|
nrows: int = 0, ncols: int = 1,
|
|
1211
|
-
fig: object | None = None,
|
|
1212
|
-
ax: object | None = None,
|
|
1261
|
+
fig: object | None = None, ax: object | None = None,
|
|
1213
1262
|
getfigax: bool = False,
|
|
1214
|
-
savefig: dict | str | None = None,
|
|
1215
|
-
show: bool = False,
|
|
1263
|
+
savefig: dict | str | None = None, show: bool = False,
|
|
1216
1264
|
**kwargs) -> tuple[object, object]:
|
|
1217
1265
|
"""Use Axes.plot of matplotlib to plot line profiles at given coordinates. kwargs must include the arguments of AstroData to specify the data to be plotted. kwargs must include the arguments of AstroFrame to specify the ranges and so on for plotting. kwargs can include the arguments of PlotAxes2D to adjust x and y axes.
|
|
1218
1266
|
|
|
@@ -1239,63 +1287,29 @@ def plotprofile(coords: list[str] | str = [],
|
|
|
1239
1287
|
Returns:
|
|
1240
1288
|
tuple: (fig, ax), where ax is a list, if getfigax=True. Otherwise, no return.
|
|
1241
1289
|
"""
|
|
1242
|
-
_kw = {
|
|
1290
|
+
_kw = {"drawstyle": "steps-mid", "color": "k",
|
|
1291
|
+
"xlabel": r"Velocity (km s$^{-1}$)", "samexy": False}
|
|
1243
1292
|
_kw.update(kwargs)
|
|
1244
1293
|
_kwgauss = {'drawstyle': 'default', 'color': 'g'}
|
|
1245
1294
|
_kwgauss.update(gauss_kwargs)
|
|
1246
|
-
|
|
1247
|
-
coords
|
|
1248
|
-
|
|
1249
|
-
d = kwargs2instance(AstroData, _kw)
|
|
1250
|
-
f.read(d)
|
|
1251
|
-
d.binning([width, 1, 1])
|
|
1252
|
-
v, prof, gfitres = d.profile(coords=coords, xlist=xlist, ylist=ylist,
|
|
1253
|
-
ellipse=ellipse, ninterp=ninterp,
|
|
1254
|
-
flux=flux, gaussfit=gaussfit)
|
|
1295
|
+
v, prof, gfitres, pa2, ylabel \
|
|
1296
|
+
= _prep_plotprofile(width, coords, xlist, ylist, ellipse,
|
|
1297
|
+
ninterp, flux, gaussfit, _kw)
|
|
1255
1298
|
nprof = len(prof)
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
elif d.Tb:
|
|
1259
|
-
ylabel = r'$T_b$ (K)'
|
|
1260
|
-
elif flux:
|
|
1261
|
-
ylabel = 'Flux (Jy)'
|
|
1262
|
-
else:
|
|
1263
|
-
ylabel = d.bunit
|
|
1264
|
-
if type(ylabel) is str:
|
|
1265
|
-
ylabel = [ylabel] * nprof
|
|
1266
|
-
|
|
1267
|
-
def gauss(x, p, c, w):
|
|
1268
|
-
return p * np.exp(-4. * np.log(2.) * ((x - c) / w)**2)
|
|
1269
|
-
|
|
1270
|
-
set_rcparams(20, 'w')
|
|
1271
|
-
if ncols == 1:
|
|
1272
|
-
nrows = nprof
|
|
1273
|
-
if fig is None:
|
|
1274
|
-
fig = plt.figure(figsize=(6 * ncols, 3 * nrows))
|
|
1275
|
-
if nprof > 1 and ax is not None:
|
|
1276
|
-
print('External ax is supported only when len(coords)=1.')
|
|
1277
|
-
ax = None
|
|
1278
|
-
ax = np.empty(nprof, dtype='object') if ax is None else [ax]
|
|
1279
|
-
if 'xlabel' not in _kw:
|
|
1280
|
-
_kw['xlabel'] = 'Velocity (km s$^{-1}$)'
|
|
1281
|
-
if 'xlim' not in _kw:
|
|
1282
|
-
_kw['xlim'] = [v.min(), v.max()]
|
|
1283
|
-
_kw['samexy'] = False
|
|
1284
|
-
pa2 = kwargs2instance(PlotAxes2D, _kw)
|
|
1299
|
+
set_rcparams(20, "w")
|
|
1300
|
+
fig, ax = _set_figax_plotprofile(fig, ax, nrows, ncols, nprof)
|
|
1285
1301
|
for i in range(nprof):
|
|
1286
|
-
sharex = None if i < nrows - 1 else ax[i - 1]
|
|
1287
|
-
ax[i] = fig.add_subplot(nrows, ncols, i + 1, sharex=sharex)
|
|
1288
1302
|
if gaussfit:
|
|
1289
|
-
ax[i].plot(v,
|
|
1303
|
+
ax[i].plot(v, gaussian1d(v, *gfitres["best"][i]), **_kwgauss)
|
|
1290
1304
|
ax[i].plot(v, prof[i], **_kw)
|
|
1291
|
-
ax[i].hlines([0], v.min(), v.max(), linestyle=
|
|
1305
|
+
ax[i].hlines([0], v.min(), v.max(), linestyle="dashed", color="k")
|
|
1292
1306
|
ax[i].set_ylabel(ylabel[i])
|
|
1293
1307
|
pa2.set_xyaxes(ax[i])
|
|
1294
1308
|
if text is not None:
|
|
1295
1309
|
ax[i].text(**text[i])
|
|
1296
1310
|
if title is not None:
|
|
1297
|
-
if
|
|
1298
|
-
title[i] = {
|
|
1311
|
+
if isinstance(title[i], str):
|
|
1312
|
+
title[i] = {"label": title[i]}
|
|
1299
1313
|
ax[i].set_title(**title[i])
|
|
1300
1314
|
if i <= nprof - ncols - 1:
|
|
1301
1315
|
plt.setp(ax[i].get_xticklabels(), visible=False)
|
|
@@ -1369,8 +1383,69 @@ def plotslice(length: float, dx: float | None = None, pa: float = 0,
|
|
|
1369
1383
|
close_figure(fig, savefig, show)
|
|
1370
1384
|
|
|
1371
1385
|
|
|
1386
|
+
def _plot_on_wall(d: AstroData, x: np.ndarray, y: np.ndarray, v: np.ndarray,
|
|
1387
|
+
measure: object, datalist: list,
|
|
1388
|
+
sign: int, axis: int, **kwargs):
|
|
1389
|
+
dx, dy, dv = x[1] - x[0], y[1] - y[0], v[1] - v[0]
|
|
1390
|
+
s, ds = [x, y, v], [dx, dy, dv]
|
|
1391
|
+
if kwargs == {}:
|
|
1392
|
+
return
|
|
1393
|
+
|
|
1394
|
+
match axis:
|
|
1395
|
+
case 2:
|
|
1396
|
+
shape = np.shape(d.data[:, :, 0])
|
|
1397
|
+
case 1:
|
|
1398
|
+
shape = np.shape(d.data[:, 0, :])
|
|
1399
|
+
case 0:
|
|
1400
|
+
shape = np.shape(d.data[0, :, :])
|
|
1401
|
+
if np.shape(kwargs['data']) != shape:
|
|
1402
|
+
print('The shape of the 2D data is inconsistent'
|
|
1403
|
+
+ ' with the shape of the 3D data.')
|
|
1404
|
+
return
|
|
1405
|
+
|
|
1406
|
+
_kw = {'levels': [3, 6, 12, 24, 48, 96, 192, 384],
|
|
1407
|
+
'sigma': 'hist', 'cmap': 'Jet', 'alpha': 0.3}
|
|
1408
|
+
_kw.update(kwargs)
|
|
1409
|
+
volume = _kw['data']
|
|
1410
|
+
levels = _kw['levels']
|
|
1411
|
+
cmap = _kw['cmap']
|
|
1412
|
+
alpha = _kw['alpha']
|
|
1413
|
+
sigma = estimate_rms(data=volume, sigma=_kw['sigma'])
|
|
1414
|
+
volume[np.isnan(volume)] = 0
|
|
1415
|
+
a = int(sign == -1)
|
|
1416
|
+
b = int(sign == 1)
|
|
1417
|
+
volume = np.moveaxis([volume * a, volume * b], 0, axis)
|
|
1418
|
+
if d.dx < 0:
|
|
1419
|
+
volume = volume[:, :, ::-1]
|
|
1420
|
+
if d.dy < 0:
|
|
1421
|
+
volume = volume[:, ::-1, :]
|
|
1422
|
+
if d.dv < 0:
|
|
1423
|
+
volume = volume[::-1, :, :]
|
|
1424
|
+
for lev in levels:
|
|
1425
|
+
if lev * sigma > np.max(volume):
|
|
1426
|
+
continue
|
|
1427
|
+
vertices, simplices, _, _ = measure.marching_cubes(volume, lev * sigma)
|
|
1428
|
+
Xg, Yg, Zg = [t[0] + i * dt for t, i, dt
|
|
1429
|
+
in zip(s, vertices.T[::-1], ds)]
|
|
1430
|
+
match axis:
|
|
1431
|
+
case 2:
|
|
1432
|
+
Xg = Xg * 0 + (x[-1] if sign == 1 else x[0])
|
|
1433
|
+
case 1:
|
|
1434
|
+
Yg = Yg * 0 + (y[-1] if sign == 1 else y[0])
|
|
1435
|
+
case 0:
|
|
1436
|
+
Zg = Zg * 0 + (v[-1] if sign == 1 else v[0])
|
|
1437
|
+
i, j, k = simplices.T
|
|
1438
|
+
mesh2d = dict(type='mesh3d', x=Xg, y=Yg, z=Zg,
|
|
1439
|
+
i=i, j=j, k=k,
|
|
1440
|
+
intensity=Zg * 0 + lev,
|
|
1441
|
+
colorscale=cmap, reversescale=False,
|
|
1442
|
+
cmin=np.min(levels), cmax=np.max(levels),
|
|
1443
|
+
opacity=alpha, name='', showscale=False)
|
|
1444
|
+
datalist.append(mesh2d)
|
|
1445
|
+
|
|
1446
|
+
|
|
1372
1447
|
def plot3d(levels: list[float] = [3, 6, 12],
|
|
1373
|
-
cmap: str = '
|
|
1448
|
+
cmap: str = 'jet', alpha: float = 0.08,
|
|
1374
1449
|
xlabel: str = 'R.A. (arcsec)',
|
|
1375
1450
|
ylabel: str = 'Dec. (arcsec)',
|
|
1376
1451
|
vlabel: str = 'Velocity (km/s)',
|
|
@@ -1461,67 +1536,11 @@ def plot3d(levels: list[float] = [3, 6, 12],
|
|
|
1461
1536
|
name='', line=dict(color='rgb(0,0,0)', width=1))
|
|
1462
1537
|
data.append(lines)
|
|
1463
1538
|
|
|
1464
|
-
def plot_on_wall(sign: int, axis: int, **kwargs):
|
|
1465
|
-
if kwargs == {}:
|
|
1466
|
-
return
|
|
1467
|
-
|
|
1468
|
-
match axis:
|
|
1469
|
-
case 2:
|
|
1470
|
-
shape = np.shape(d.data[:, :, 0])
|
|
1471
|
-
case 1:
|
|
1472
|
-
shape = np.shape(d.data[:, 0, :])
|
|
1473
|
-
case 0:
|
|
1474
|
-
shape = np.shape(d.data[0, :, :])
|
|
1475
|
-
if np.shape(kwargs['data']) != shape:
|
|
1476
|
-
print('The shape of the 2D data is inconsistent'
|
|
1477
|
-
+ ' with the shape of the 3D data.')
|
|
1478
|
-
return
|
|
1479
|
-
|
|
1480
|
-
_kw = {'levels': [3, 6, 12, 24, 48, 96, 192, 384],
|
|
1481
|
-
'sigma': 'hist', 'cmap': 'Jet', 'alpha': 0.3}
|
|
1482
|
-
_kw.update(kwargs)
|
|
1483
|
-
volume = _kw['data']
|
|
1484
|
-
levels = _kw['levels']
|
|
1485
|
-
cmap = _kw['cmap']
|
|
1486
|
-
alpha = _kw['alpha']
|
|
1487
|
-
sigma = estimate_rms(data=volume, sigma=_kw['sigma'])
|
|
1488
|
-
volume[np.isnan(volume)] = 0
|
|
1489
|
-
a = int(sign == -1)
|
|
1490
|
-
b = int(sign == 1)
|
|
1491
|
-
volume = np.moveaxis([volume * a, volume * b], 0, axis)
|
|
1492
|
-
if d.dx < 0:
|
|
1493
|
-
volume = volume[:, :, ::-1]
|
|
1494
|
-
if d.dy < 0:
|
|
1495
|
-
volume = volume[:, ::-1, :]
|
|
1496
|
-
if d.dv < 0:
|
|
1497
|
-
volume = volume[::-1, :, :]
|
|
1498
|
-
for lev in levels:
|
|
1499
|
-
if lev * sigma > np.max(volume):
|
|
1500
|
-
continue
|
|
1501
|
-
vertices, simplices, _, _ = measure.marching_cubes(volume, lev * sigma)
|
|
1502
|
-
Xg, Yg, Zg = [t[0] + i * dt for t, i, dt
|
|
1503
|
-
in zip(s, vertices.T[::-1], ds)]
|
|
1504
|
-
match axis:
|
|
1505
|
-
case 2:
|
|
1506
|
-
Xg = Xg * 0 + (x[-1] if sign == 1 else x[0])
|
|
1507
|
-
case 1:
|
|
1508
|
-
Yg = Yg * 0 + (y[-1] if sign == 1 else y[0])
|
|
1509
|
-
case 0:
|
|
1510
|
-
Zg = Zg * 0 + (v[-1] if sign == 1 else v[0])
|
|
1511
|
-
i, j, k = simplices.T
|
|
1512
|
-
mesh2d = dict(type='mesh3d', x=Xg, y=Yg, z=Zg,
|
|
1513
|
-
i=i, j=j, k=k,
|
|
1514
|
-
intensity=Zg * 0 + lev,
|
|
1515
|
-
colorscale=cmap, reversescale=False,
|
|
1516
|
-
cmin=np.min(levels), cmax=np.max(levels),
|
|
1517
|
-
opacity=alpha, name='', showscale=False)
|
|
1518
|
-
data.append(mesh2d)
|
|
1519
|
-
|
|
1520
1539
|
klist = [xplus, xminus, yplus, yminus, vplus, vminus]
|
|
1521
1540
|
slist = [1, -1, 1, -1, 1, -1]
|
|
1522
1541
|
alist = [2, 2, 1, 1, 0, 0]
|
|
1523
1542
|
for kw, sign, axis in zip(klist, slist, alist):
|
|
1524
|
-
|
|
1543
|
+
_plot_on_wall(d, x, y, v, measure, data, sign, axis, **kw)
|
|
1525
1544
|
|
|
1526
1545
|
if return_data_layout:
|
|
1527
1546
|
return {'data': data, 'layout': layout}
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.9
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|