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.
Files changed (25) hide show
  1. {plotastrodata-1.9.9/plotastrodata.egg-info → plotastrodata-1.9.11}/PKG-INFO +1 -1
  2. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/__init__.py +1 -1
  3. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/analysis_utils.py +135 -123
  4. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/coord_utils.py +5 -5
  5. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fits_utils.py +20 -20
  6. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fitting_utils.py +101 -77
  7. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/noise_utils.py +4 -4
  8. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/other_utils.py +4 -4
  9. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/plot_utils.py +58 -74
  10. {plotastrodata-1.9.9 → plotastrodata-1.9.11/plotastrodata.egg-info}/PKG-INFO +1 -1
  11. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/LICENSE +0 -0
  12. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/MANIFEST.in +0 -0
  13. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/README.md +0 -0
  14. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/const_utils.py +0 -0
  15. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/ext_utils.py +0 -0
  16. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/fft_utils.py +0 -0
  17. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/los_utils.py +0 -0
  18. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata/matrix_utils.py +0 -0
  19. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/SOURCES.txt +0 -0
  20. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/dependency_links.txt +0 -0
  21. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/not-zip-safe +0 -0
  22. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/requires.txt +0 -0
  23. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/plotastrodata.egg-info/top_level.txt +0 -0
  24. {plotastrodata-1.9.9 → plotastrodata-1.9.11}/setup.cfg +0 -0
  25. {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.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
@@ -1,4 +1,4 @@
1
1
  import warnings
2
2
 
3
3
  warnings.simplefilter('ignore', FutureWarning)
4
- __version__ = '1.9.9'
4
+ __version__ = '1.9.11'
@@ -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
- dx, dy = x[1] - x[0], y[1] - y[0]
37
- nx = int(np.floor(max(np.abs(x[0]), np.abs(x[-1])) / dx))
38
- ny = int(np.floor(max(np.abs(y[0]), np.abs(y[-1])) / dy))
39
- xnew = np.linspace(-nx * dx, nx * dx, 2 * nx + 1)
40
- ynew = np.linspace(-ny * dy, ny * dy, 2 * ny + 1)
41
- Xnew, Ynew = np.meshgrid(xnew, ynew)
42
- if quadrants in ['13', '24']:
43
- s = 1 if quadrants == '13' else -1
44
- f = RGI((y, s * x), data, bounds_error=False, fill_value=np.nan)
45
- datanew = f((Ynew, Xnew))
46
- else:
47
- print('quadrants must be \'13\' or \'24\'.')
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(self, *args, **kwargs):
74
- singlepixel = self.dx is None or self.dy is None
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(self, *args, **kwargs)
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 type(self.fitsimage) is not list:
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 type(self.data) is not list:
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 binning(self, width: list[int, int, int] = [1, 1, 1]):
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] * (4 - len(width)) + list(width)
176
+ w = np.array([1] * (3 - len(width)) + list(width), dtype=int)
150
177
  if self.pv:
151
- w[2] = max(w[1], w[2])
152
- w[1] = 1
153
- d = to4dim(self.data)
154
- size = np.array(np.shape(d))
155
- w = np.array(w, dtype=int)
156
- if np.any(w > size):
157
- w = np.minimum(w, size)
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
- newsize = size // w
161
- if (not self.pv and w[1] > 1) or (self.pv and w[2] > 1):
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[2] > 1) or w[3] > 1:
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
- grid = [None, self.v, self.y, self.x]
169
- dgrid = [None, self.dv, self.dy, self.dx]
170
- for n in range(1, 4):
171
- if w[n] == 1 or grid[n] is None:
172
- continue
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
- d = to4dim(d)
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), Mrot(pa-bpa),
276
- Mfac(1, ci), Mrot(-pa)]).T
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
- if gaussfit:
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
- def indeg(s):
540
- u = f'CUNIT{s}'
541
- return fhd is None or u not in fhd or isdeg(fhd[u])
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.v[ck]
551
- dv = self.dv
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 = ["fitsimage", "data", "Tb", "sigma", "center", "restfreq",
594
- "cfactor", "bunit", "fitsimage_org", "sigma_org",
595
- "beam_org", "fitsheader", "pv", "pvpa"]
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 type(poslist[0]) is not str
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 type(p) is str:
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 "RESTFRQ" in header:
692
- return header["RESTFRQ"]
693
- if "RESTFREQ" in header:
694
- return header["RESTFREQ"]
695
- if "NAXIS3" in header and header["NAXIS3"] == 1 and not self.pv:
696
- return header["CRVAL3"]
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("BUNIT")
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"d{axis}", _get_gridsep(getattr(d, axis)))
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 = {"CDELT1": dx / 3600,
770
- "CUNIT1": "DEG",
771
- "RESTFREQ": d.restfreq[i]}
781
+ header = {'CDELT1': dx / 3600,
782
+ 'CUNIT1': 'deg',
783
+ 'RESTFREQ': d.restfreq[i]}
772
784
  if None not in d.beam[i]:
773
- header["BMAJ"] = d.beam[i][0] / 3600 / self.dist
774
- header["BMIN"] = d.beam[i][1] / 3600 / self.dist
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("pvpa is not specified. pvpa=bpa is assumed.")
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 + ["beam"]:
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 type(frame) is str:
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 type(coord) is str:
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 type(coords) is str:
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["CRVAL1"], h["CRVAL2"]])
245
- if (radesys := h.get("RADESYS")) is not None:
246
- coordorg = f"{radesys} {coordorg}"
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 = ["x", "y"]
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"NAXIS{i+1}") is None or s_in is None:
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"CUNIT{i+1}"]):
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"{slabel[i]}", s)
260
- setattr(self, f"d{slabel[i]}", ds)
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 = "2" if pv else "3"
270
- if h.get(f"NAXIS{vaxis}") is None or v_in is None:
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("restfreq is assumed to be the center.")
276
+ print('restfreq is assumed to be the center.')
277
277
  else:
278
278
  freq = restfreq
279
- v = v_in + h[f"CRVAL{vaxis}"]
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 "Hz" | "HZ":
283
+ case 'Hz' | 'HZ':
284
284
  if freq == 0:
285
- print("v is read as is, because restfreq=0.")
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 "m/s" | "M/S":
288
+ case 'm/s' | 'M/S':
289
289
  print(f'{key}={cunitv} found.')
290
290
  v = v * 1e-3 - vsys
291
- case 'km/s' | "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"NAXIS{i:d}")
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"CRPIX{i:d}"] + 1) * h[f"CDELT{i:d}"]
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 ["x", "y", "v"]]):
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
- tuple[float, float, float], float, float]:
379
+ np.ndarray, str, float]:
380
380
  """Extract data from a fits file. kwargs are arguments of FitsData.trim().
381
381
 
382
382
  Args: