plotastrodata 1.8.15__tar.gz → 1.8.16__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.8.15/plotastrodata.egg-info → plotastrodata-1.8.16}/PKG-INFO +1 -1
  2. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/__init__.py +1 -1
  3. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/analysis_utils.py +18 -33
  4. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/fitting_utils.py +116 -27
  5. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/other_utils.py +0 -25
  6. {plotastrodata-1.8.15 → plotastrodata-1.8.16/plotastrodata.egg-info}/PKG-INFO +1 -1
  7. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/LICENSE +0 -0
  8. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/MANIFEST.in +0 -0
  9. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/README.md +0 -0
  10. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/const_utils.py +0 -0
  11. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/coord_utils.py +0 -0
  12. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/ext_utils.py +0 -0
  13. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/fft_utils.py +0 -0
  14. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/fits_utils.py +0 -0
  15. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/los_utils.py +0 -0
  16. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/matrix_utils.py +0 -0
  17. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/noise_utils.py +0 -0
  18. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata/plot_utils.py +0 -0
  19. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata.egg-info/SOURCES.txt +0 -0
  20. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata.egg-info/dependency_links.txt +0 -0
  21. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata.egg-info/not-zip-safe +0 -0
  22. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata.egg-info/requires.txt +0 -0
  23. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/plotastrodata.egg-info/top_level.txt +0 -0
  24. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/setup.cfg +0 -0
  25. {plotastrodata-1.8.15 → plotastrodata-1.8.16}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotastrodata
3
- Version: 1.8.15
3
+ Version: 1.8.16
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.8.15'
4
+ __version__ = '1.8.16'
@@ -2,17 +2,16 @@ import numpy as np
2
2
  import warnings
3
3
  from dataclasses import dataclass
4
4
  from scipy.interpolate import RegularGridInterpolator as RGI
5
- from scipy.optimize import curve_fit
6
5
  from scipy.signal import convolve
7
6
 
8
7
  from plotastrodata import const_utils as cu
9
8
  from plotastrodata.coord_utils import coord2xy, rel2abs, xy2coord
10
9
  from plotastrodata.fits_utils import data2fits, FitsData, Jy2K
11
- from plotastrodata.fitting_utils import EmceeCorner, gaussfit1d
10
+ from plotastrodata.fitting_utils import (EmceeCorner, gaussian2d,
11
+ gaussfit1d, gaussfit2d)
12
12
  from plotastrodata.matrix_utils import dot2d, Mfac, Mrot
13
13
  from plotastrodata.noise_utils import estimate_rms
14
- from plotastrodata.other_utils import (gaussian2d, isdeg,
15
- RGIxy, RGIxyv, to4dim, trim)
14
+ from plotastrodata.other_utils import isdeg, RGIxy, RGIxyv, to4dim, trim
16
15
 
17
16
 
18
17
  def quadrantmean(data: np.ndarray, x: np.ndarray, y: np.ndarray,
@@ -106,7 +105,7 @@ class AstroData():
106
105
  beam: np.ndarray | tuple[None] = (None, None, None)
107
106
  fitsimage: str | None = None
108
107
  Tb: bool = False
109
- sigma: str = 'hist'
108
+ sigma: str | float | None = 'hist'
110
109
  center: str = 'common'
111
110
  restfreq: float | None = None
112
111
  cfactor: float = 1
@@ -333,47 +332,32 @@ class AstroData():
333
332
 
334
333
  @_need_multipixels
335
334
  def gaussfit2d(self, chan: int | None = None) -> dict:
336
- """Fit a 2D Gaussian function to self.data.
335
+ """Fit a 2D Gaussian function to self.data using fitting_utils.gaussfit2d().
337
336
 
338
337
  Args:
339
338
  chan (int): The channel number where the 2D Gaussian is fitted. Defaults to None.
340
339
 
341
340
  Returns:
342
- dict: The best parameter set (popt), the covariance set (pcov), the best 2D Gaussian array (model), the residual from the model (residual), and the coordinates of the best-fit center (center).
341
+ dict: The best parameter set (popt), the error set (perr), the best 2D Gaussian array (model), the residual from the model (residual), and the coordinates of the best-fit center (center).
343
342
  """
344
- d = self.data if chan is None else self.data[chan]
345
- x = self.x
346
- y = self.y
347
- ds = np.min([np.abs(self.dx), np.abs(self.dy)])
348
- p0 = (np.max(d), np.median(x), np.median(y), 5 * ds, 5 * ds, 0)
349
- amax = np.max(np.abs(d))
350
- xmin = np.min(x)
351
- xmax = np.max(x)
352
- ymin = np.min(y)
353
- ymax = np.max(y)
354
- smax = np.max([xmax - xmin, ymax - ymin])
355
- bounds = [[-amax, xmin, ymin, ds, ds, -90],
356
- [amax, xmax, ymax, smax, smax, 90]]
357
- x, y = np.meshgrid(x, y)
343
+ z = self.data if chan is None else self.data[chan]
358
344
  Omega = np.pi * self.beam[0] * self.beam[1] / 4 / np.log(2)
359
345
  pixelperbeam = Omega / np.abs(self.dx * self.dy)
360
346
  s = 'sigma is multiplied by sqrt(pixel-per-beam)' \
361
347
  + ' to consider the noise correlation in a beam.' \
362
348
  + ' This correction is relatively conservative.'
363
349
  warnings.warn(s, UserWarning)
364
- popt, pcov = curve_fit(gaussian2d,
365
- (x.ravel(), y.ravel()), d.ravel(),
366
- p0=p0, bounds=bounds,
367
- sigma=self.sigma * np.sqrt(pixelperbeam),
368
- absolute_sigma=True)
369
- model = gaussian2d((x, y), *popt)
370
- residual = d - model
371
- if (center := self.center) is not None:
372
- xy = popt[1:3] / 3600
373
- newcenter = xy2coord(xy, coordorg=center)
350
+ res = gaussfit2d(xdata=self.x, ydata=self.y, zdata=z,
351
+ sigma=self.sigma * np.sqrt(pixelperbeam),
352
+ show=False, nwalkersperdim=4)
353
+ popt, perr = res['popt'], res['perr']
354
+ model = gaussian2d(np.meshgrid(self.x, self.y), *popt)
355
+ residual = z - model
356
+ if self.center is not None:
357
+ newcenter = xy2coord(popt[1:3] / 3600, coordorg=self.center)
374
358
  else:
375
359
  newcenter = None
376
- return {'popt': popt, 'pcov': pcov,
360
+ return {'popt': popt, 'perr': perr,
377
361
  'model': model, 'residual': residual,
378
362
  'center': newcenter}
379
363
 
@@ -468,7 +452,8 @@ class AstroData():
468
452
  res = [None] * nprof
469
453
  for i in range(nprof):
470
454
  res[i] = gaussfit1d(xdata=v, ydata=prof[i],
471
- sigma=None, show=True)
455
+ sigma=None, show=True,
456
+ nwalkersperdim=8)
472
457
  gfitres['best'] = [a['popt'][:3] for a in res]
473
458
  gfitres['error'] = [a['perr'][:3] for a in res]
474
459
  return v, prof, gfitres
@@ -8,6 +8,7 @@ from dynesty import DynamicNestedSampler as DNS
8
8
  from multiprocessing import Pool
9
9
  from tqdm import tqdm
10
10
 
11
+ from plotastrodata.matrix_utils import Mrot, dot2d
11
12
  from plotastrodata.other_utils import close_figure
12
13
 
13
14
 
@@ -395,6 +396,47 @@ class EmceeCorner():
395
396
  return {'evidence': evidence, 'error': error}
396
397
 
397
398
 
399
+ def gaussian1d(x: np.ndarray | float,
400
+ amplitude: float, xo: float, fwhm: float,
401
+ ) -> np.ndarray:
402
+ """One dimensional Gaussian function.
403
+
404
+ Args:
405
+ x (np.ndarray): Variable of the Gaussian function.
406
+ amplitude (float): Peak value.
407
+ xo (float): Offset in the x direction.
408
+ fwhm (float): Full width at half maximum.
409
+
410
+ Returns:
411
+ g (np.ndarray): 1D numpy array.
412
+ """
413
+ g = amplitude * np.exp2(-4 * (((x - xo) / fwhm)**2))
414
+ return g
415
+
416
+
417
+ def gaussian2d(xy: np.ndarray,
418
+ amplitude: float, xo: float, yo: float,
419
+ fwhm_major: float, fwhm_minor: float, pa: float
420
+ ) -> np.ndarray:
421
+ """Two dimensional Gaussian function.
422
+
423
+ Args:
424
+ xy (np.ndarray): A pair of (x, y).
425
+ amplitude (float): Peak value.
426
+ xo (float): Offset in the x direction.
427
+ yo (float): Offset in the y direction.
428
+ fwhm_major (float): Full width at half maximum in the major axis (but can be shorter than the minor axis).
429
+ fwhm_minor (float): Full width at half maximum in the minor axis (but can be longer then the major axis).
430
+ pa (float): Position angle of the major axis from the +y axis to the +x axis in the unit of degree.
431
+
432
+ Returns:
433
+ g (np.ndarray): Output array in the same shape as xy.
434
+ """
435
+ s, t = dot2d(Mrot(-pa), [xy[1] - yo, xy[0] - xo])
436
+ g = amplitude * np.exp2(-4 * ((s / fwhm_major)**2 + (t / fwhm_minor)**2))
437
+ return g
438
+
439
+
398
440
  def gaussfit1d(xdata: np.ndarray, ydata: np.ndarray,
399
441
  sigma: float | np.ndarray | None,
400
442
  show: bool = False, **kwargs) -> dict:
@@ -403,43 +445,90 @@ def gaussfit1d(xdata: np.ndarray, ydata: np.ndarray,
403
445
  Args:
404
446
  xdata (np.ndarray): ydata is compared with Gauss(xdata).
405
447
  ydata (np.ndarray): ydata is compared with Gauss(xdata).
406
- sigma (float | np.ndarray | None): Noise level of ydata. If None is given, sigma is also a free parameter. Defaults to None.
448
+ sigma (float | np.ndarray | None): Noise level of ydata. If None is given, sigma is estimated by a temporary fitting. Defaults to None.
407
449
  show (bool, optional): True means to show the best-fit parameters and uncertainties. Defaults to False.
408
450
 
409
451
  Returns:
410
- dict: _description_
452
+ dict: The keys are popt, perr, and sigma.
411
453
  """
412
- if sigma is not None and np.shape(sigma) == ():
413
- sigma = [sigma] * len(xdata)
414
454
  xmin, xmax = np.min(xdata), np.max(xdata)
415
455
  ymin, ymax = np.min(ydata), np.max(ydata)
456
+ xw = xmax - xmin
457
+ yw = ymax - ymin
416
458
  dx = np.abs(xdata[1] - xdata[0])
417
- bounds = [[ymin, ymax], [xmin, xmax], [dx, xmax - xmin]]
418
- if sigma is None:
419
- bounds.append([np.log(ymax * 1e-6), np.log(ymax)])
420
-
421
- def g(x, p):
422
- a, c, w = p
423
- return a * np.exp(-4. * np.log(2.) * ((x - c) / w)**2)
424
-
425
- if sigma is None:
426
- def logl(p):
427
- sigmdl = np.exp(p[3])
428
- chi2 = np.sum(((ydata - g(xdata, p[:3])) / sigmdl)**2)
429
- return -0.5 * chi2 - p[3]
430
- else:
431
- def logl(p):
432
- chi2 = np.sum(((ydata - g(xdata, p)) / sigma)**2)
433
- return -0.5 * chi2
434
-
435
- fitter = EmceeCorner(bounds=bounds, logl=logl)
436
- fitter.fit(**kwargs)
459
+ bounds = [[ymin - yw * 10, ymax + yw * 10], [xmin, xmax], [dx, xw]]
460
+ sigtmp = sigma or max(np.abs(ymin), np.abs(ymax)) * 0.01
461
+ for i in range(2 if sigma is None else 1):
462
+ fitter = EmceeCorner(bounds=bounds, model=gaussian1d,
463
+ sigma=sigtmp, xdata=xdata, ydata=ydata)
464
+ fitter.fit(**kwargs)
465
+ if i == 0:
466
+ sigtmp = np.std(ydata - gaussian1d(xdata, *fitter.popt))
437
467
  popt = fitter.popt
438
468
  plow = fitter.plow
439
469
  phigh = fitter.phigh
440
470
  perr = (phigh - plow) / 2
441
- pars = 'peak, center, FWHM' + (', lnsigma' if sigma is None else '')
442
471
  if show:
443
- print(f'Gauss ({pars}):', popt)
472
+ print('Gauss (peak, center, FWHM):', popt)
473
+ print('Gauss uncertainties:', perr)
474
+ if sigma is None:
475
+ print('Estimated sigma: ', sigtmp)
476
+ return {'popt': popt, 'perr': perr, 'sigma': sigtmp}
477
+
478
+
479
+ def gaussfit2d(xdata: np.ndarray, ydata: np.ndarray, zdata: np.ndarray,
480
+ sigma: float | np.ndarray | None,
481
+ show: bool = False, **kwargs) -> dict:
482
+ """Gaussian fitting to a pair of 1D arrays.
483
+
484
+ Args:
485
+ xdata (np.ndarray): zdata is compared with Gauss(xdata, ydata).
486
+ ydata (np.ndarray): zdata is compared with Gauss(xdata, ydata).
487
+ zdata (np.ndarray): zdata is compared with Gauss(xdata, ydata).
488
+ sigma (float | np.ndarray | None): Noise level of ydata. If None is given, sigma is estimated by a temporary fitting. Defaults to None.
489
+ show (bool, optional): True means to show the best-fit parameters and uncertainties. Defaults to False.
490
+
491
+ Returns:
492
+ dict: The keys are popt, perr, and sigma.
493
+ """
494
+ xmin, xmax = np.min(xdata), np.max(xdata)
495
+ ymin, ymax = np.min(ydata), np.max(ydata)
496
+ zmin, zmax = np.min(zdata), np.max(zdata)
497
+ xw = xmax - xmin
498
+ yw = ymax - ymin
499
+ zw = zmax - zmin
500
+ dx = min(np.abs(xdata[1] - xdata[0]), np.abs(ydata[1] - ydata[0]))
501
+ xw = max(xw, yw)
502
+ xy = np.meshgrid(xdata, ydata)
503
+ sigtmp = sigma or max(np.abs(zmin), np.abs(zmax)) * 0.01
504
+
505
+ def model(xy, a, cx, cy, wmaj, wmin, pa):
506
+ if wmaj < wmin:
507
+ return np.inf
508
+ else:
509
+ return gaussian2d(xy, a, cx, cy, wmaj, wmin, pa)
510
+
511
+ bounds = [[zmin - zw * 10, zmax + zw * 10],
512
+ [xmin, xmax], [ymin, ymax],
513
+ [dx, xw], [dx, xw], [-90, 90]]
514
+ for i in range(2):
515
+ fitter = EmceeCorner(bounds=bounds, model=model,
516
+ sigma=sigtmp, xdata=xy, ydata=zdata)
517
+ fitter.fit(**kwargs)
518
+ a, cx, cy, wmaj, wmin, pa = fitter.popt
519
+ wmax = max(wmaj, wmin)
520
+ bounds = [sorted([a / 2, a * 2]),
521
+ [cx - wmax, cx + wmax], [cy - wmax, cy + wmax],
522
+ [wmaj / 2, wmaj * 2], [wmin / 2, wmin * 2],
523
+ [pa - 45, pa + 45]]
524
+ if i == 0 and sigma is None:
525
+ sigtmp = np.std(ydata - gaussian2d(xy, *fitter.popt))
526
+ popt = fitter.popt
527
+ popt[-1] = (popt[-1] + 90) % 180 - 90
528
+ perr = (fitter.phigh - fitter.plow) / 2
529
+ if show:
530
+ print('Gauss (peak, center, FWHM):', popt)
444
531
  print('Gauss uncertainties:', perr)
445
- return {'popt': popt, 'perr': perr}
532
+ if sigma is None:
533
+ print('Estimated sigma: ', sigtmp)
534
+ return {'popt': popt, 'perr': perr, 'sigma': sigtmp}
@@ -2,8 +2,6 @@ import matplotlib.pyplot as plt
2
2
  import numpy as np
3
3
  from scipy.interpolate import RegularGridInterpolator as RGI
4
4
 
5
- from plotastrodata.matrix_utils import Mrot, dot2d
6
-
7
5
 
8
6
  def listing(*args) -> list:
9
7
  """Output a list of the input when the input is string or number.
@@ -176,29 +174,6 @@ def RGIxyv(v: np.ndarray, y: np.ndarray, x: np.ndarray, data: np.ndarray,
176
174
  return np.squeeze([f3d(tuple(vyxnew)) for f3d in f])
177
175
 
178
176
 
179
- def gaussian2d(xy: np.ndarray,
180
- amplitude: float, xo: float, yo: float,
181
- fwhm_major: float, fwhm_minor: float, pa: float
182
- ) -> np.ndarray:
183
- """Two dimensional Gaussian function.
184
-
185
- Args:
186
- xy (np.ndarray): A pair of (x, y).
187
- amplitude (float): Peak value.
188
- xo (float): Offset in the x direction.
189
- yo (float): Offset in the y direction.
190
- fwhm_major (float): Full width at half maximum in the major axis (but can be shorter than the minor axis).
191
- fwhm_minor (float): Full width at half maximum in the minor axis (but can be longer then the major axis).
192
- pa (float): Position angle of the major axis from the +y axis to the +x axis in the unit of degree.
193
-
194
- Returns:
195
- g (np.ndarray): 2D numpy array.
196
- """
197
- s, t = dot2d(Mrot(-pa), [xy[1] - yo, xy[0] - xo])
198
- g = amplitude * np.exp2(-4 * ((s / fwhm_major)**2 + (t / fwhm_minor)**2))
199
- return g
200
-
201
-
202
177
  def close_figure(fig: object, savefig: dict | str | None = None,
203
178
  show: bool = False, tight: bool = True) -> None:
204
179
  """Save, show, and close the figure.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotastrodata
3
- Version: 1.8.15
3
+ Version: 1.8.16
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