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.
Files changed (23) hide show
  1. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/PKG-INFO +1 -1
  2. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/__init__.py +1 -1
  3. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/analysis_utils.py +50 -20
  4. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fft_utils.py +5 -2
  5. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fits_utils.py +15 -14
  6. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/fitting_utils.py +19 -8
  7. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/los_utils.py +2 -1
  8. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/other_utils.py +6 -3
  9. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/plot_utils.py +107 -72
  10. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/PKG-INFO +1 -1
  11. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/LICENSE +0 -0
  12. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/README.md +0 -0
  13. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/const_utils.py +0 -0
  14. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/coord_utils.py +0 -0
  15. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/ext_utils.py +0 -0
  16. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata/matrix_utils.py +0 -0
  17. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/SOURCES.txt +0 -0
  18. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/dependency_links.txt +0 -0
  19. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/not-zip-safe +0 -0
  20. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/requires.txt +0 -0
  21. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/plotastrodata.egg-info/top_level.txt +0 -0
  22. {plotastrodata-1.5.0 → plotastrodata-1.6.0}/setup.cfg +0 -0
  23. {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.5.0
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
@@ -1,4 +1,4 @@
1
1
  import warnings
2
2
 
3
3
  warnings.simplefilter('ignore', UserWarning)
4
- __version__ = '1.5.0'
4
+ __version__ = '1.6.0'
@@ -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') -> tuple[np.ndarray, np.ndarray, np.ndarray]:
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
- width = [1] * (4 - len(width)) + width
146
+ w = [1] * (4 - len(width)) + list(width)
136
147
  d = to4dim(self.data)
137
- size = list(np.shape(d))
138
- newsize = size // np.array(width, dtype=int)
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 width[n] == 1:
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(width[n]):
148
- t += grid[n][i:i + newsize[n]*width[n]:width[n]]
149
- newdata += olddata[i:i + newsize[n]*width[n]:width[n]]
150
- grid[n] = t / width[n]
151
- d = np.moveaxis(newdata, 0, n) / width[n]
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
- g1 /= np.sqrt(np.pi/4/np.log(2) * bmin * np.sqrt(1 - bmin**2/bmaj**2))
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
- def fit2d(self, model: object, bounds: np.ndarray, progressbar: bool = False,
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 and None not in [self.dx, self.dy]:
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
- if kwargs_plotcorner0['show'] or kwargs_plotcorner0['savefig'] is not None:
277
- mcmc.plotcorner(**kwargs_plotcorner0)
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, 'model': model, 'residual': residual}
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
- e = np.sqrt(np.diag(pcov))
443
+ perr = np.sqrt(np.diag(pcov))
419
444
  print('Gauss (peak, center, FWHM):', popt)
420
- print('Gauss uncertainties:', e)
421
- best[i], error[i] = popt, e
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, xoff: float = 0) -> 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, x0: float = None, outreal: bool = True
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
- bmaj = bmin = header['CDELT1'] * np.sqrt(4*np.log(2)/np.pi) * 3600
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(s: np.ndarray) -> None:
248
- s = (s - cx) * dist
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(s: np.ndarray) -> None:
254
- s = (s - cy) * dist
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(s: np.ndarray) -> None:
260
+ def gen_v(s_in: np.ndarray) -> None:
260
261
  if restfreq is None:
261
- freq = np.mean(s)
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 - s / freq) * cu.c_kms - vsys
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 - s / freq) * cu.c_kms - vsys
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 = s * 1e-3 - vsys
283
+ s = s_in * 1e-3 - vsys
283
284
  case 'M/S':
284
285
  print(f'{key}=\'M/S\' found.')
285
- s = s * 1e-3 - vsys
286
+ s = s_in * 1e-3 - vsys
286
287
  case 'km/s':
287
288
  print(f'{key}=\'km/s\' found.')
288
- s = s - vsys
289
+ s = s_in - vsys
289
290
  case 'KM/S':
290
291
  print(f'{key}=\'KM/S\' found.')
291
- s = s - vsys
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 = s - vsys
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, ydata: 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(f'nwalkersperdim < 2 is not allowed. Use 2 instead of {nwalkersperdim:d}.')
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, labels: list[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)), truths=self.popt,
188
- quantiles=[self.percent[0] / 100, 0.5, self.percent[1] / 100],
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, cornerrange: list[float] = None,
296
- cmap: str = 'binary', levels: list[float] = [0.011109, 0.135335, 0.606531]) -> None:
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
- return self.bounds[:, 0] + (self.bounds[:, 1] - self.bounds[:, 0]) * u
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) -> np.ndarray:
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') -> float:
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) -> tuple[np.ndarray, list[np.ndarray, np.ndarray, np.ndarray]]:
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) -> np.ndarray:
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
- rms: float, kw: dict) -> np.ndarray:
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
- c = ((c / cmin)**(1 - stretchpower) - 1) \
207
- / (1 - stretchpower) / np.log(10)
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
- kw[m][i] = ((kw[m][i]/c.min())**(1 - stretchpower) - 1) \
219
- / (1 - stretchpower) / np.log(10)
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, v: np.ndarray = np.array([0]), vskip: int = 1,
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, nrows: int = 4, ncols: int = 6,
350
- fontsize: int | None = None, nancolor: str = 'w', dpi: int = 256,
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, **kwargs) -> 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, xskip: int = 1, yskip: int = 1,
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, cblabel: str | None = None,
674
- cbformat: float = '%.1e', cbticks: list[float] | None = None,
675
- cbticklabels: list[str] | None = None, cblocation: str = 'right',
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
- 'edgecolors': 'none', 'zorder': 1}
693
- beam_kwargs = kwargs2beamargs(kwargs)
694
- _kw.update(kwargs)
695
- d = kwargs2AstroData(_kw)
696
- self.read(d, xskip, yskip)
697
- c, x, y, v, beam, sigma = d.data, d.x, d.y, d.v, d.beam, d.sigma
698
- bunit = d.bunit
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 = (np.array(cbticks) / cmin_org)**(1 - stretchpower)
736
- cbticks = (cbticks - 1) / (1 - stretchpower) / np.log(10)
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
- ticklin = 1 + (1 - stretchpower) * np.log(10) * t
750
- ticklin = cmin_org * ticklin**(1 / (1 - stretchpower))
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, xskip: int = 1, yskip: int = 1,
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 = kwargs2beamargs(kwargs)
765
- _kw.update(kwargs)
766
- d = kwargs2AstroData(_kw)
767
- self.read(d, xskip, yskip)
768
- c, x, y, v, beam, sigma = d.data, d.x, d.y, d.v, d.beam, d.sigma
769
- self.beam = beam
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, ampfits: str = None, angfits: str = None,
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
- 'pivot': 'mid', 'headwidth': 0, 'headlength': 0,
808
- 'headaxislength': 0, 'width': 0.007, 'zorder': 3}
809
- beam_kwargs = kwargs2beamargs(kwargs)
810
- _kw.update(kwargs)
811
- _kw['data'] = [amp, ang, stU, stQ]
812
- _kw['fitsimage'] = [ampfits, angfits, Ufits, Qfits]
813
- d = kwargs2AstroData(_kw)
814
- self.read(d, xskip, yskip)
815
- c, x, y, v, beam, sigma = d.data, d.x, d.y, d.v, d.beam, d.sigma
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 if len(x) == 1 else 1. / np.abs(x[1] - x[0])
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, xskip: int = 1, yskip: int = 1,
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 = kwargs2beamargs(kwargs)
859
- _kw.update(kwargs)
860
- d = kwargs2AstroData(_kw)
861
- self.read(d, xskip, yskip)
862
- c, x, y, v, beam, sigma = d.data, d.x, d.y, d.v, d.beam, d.sigma
863
- self.beam = beam
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) -> 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, text: 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 with the shape of the 3D data.')
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', auto_play=show)
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.5.0
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