plotastrodata 1.9.6__tar.gz → 1.9.7__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.6/plotastrodata.egg-info → plotastrodata-1.9.7}/PKG-INFO +1 -1
  2. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/__init__.py +1 -1
  3. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/analysis_utils.py +157 -141
  4. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/fits_utils.py +1 -1
  5. {plotastrodata-1.9.6 → plotastrodata-1.9.7/plotastrodata.egg-info}/PKG-INFO +1 -1
  6. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/LICENSE +0 -0
  7. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/MANIFEST.in +0 -0
  8. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/README.md +0 -0
  9. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/const_utils.py +0 -0
  10. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/coord_utils.py +0 -0
  11. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/ext_utils.py +0 -0
  12. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/fft_utils.py +0 -0
  13. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/fitting_utils.py +0 -0
  14. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/los_utils.py +0 -0
  15. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/matrix_utils.py +0 -0
  16. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/noise_utils.py +0 -0
  17. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/other_utils.py +0 -0
  18. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata/plot_utils.py +0 -0
  19. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata.egg-info/SOURCES.txt +0 -0
  20. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata.egg-info/dependency_links.txt +0 -0
  21. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata.egg-info/not-zip-safe +0 -0
  22. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata.egg-info/requires.txt +0 -0
  23. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/plotastrodata.egg-info/top_level.txt +0 -0
  24. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/setup.cfg +0 -0
  25. {plotastrodata-1.9.6 → plotastrodata-1.9.7}/setup.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotastrodata
3
- Version: 1.9.6
3
+ Version: 1.9.7
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.6'
4
+ __version__ = '1.9.7'
@@ -575,6 +575,26 @@ class AstroData():
575
575
  fitsimage=fitsimage)
576
576
 
577
577
 
578
+ def _as_list(value, n: int, isbeam: bool = False):
579
+ if isbeam:
580
+ return [value] * n if np.ndim(value) == 1 else value
581
+ else:
582
+ return value if isinstance(value, list) else [value] * n
583
+
584
+
585
+ def _scalar_if_single(value, n: int):
586
+ return value[0] if n == 1 else value
587
+
588
+
589
+ def _get_gridsep(axis: np.ndarray | None):
590
+ return axis[1] - axis[0] if axis is not None and len(axis) > 1 else None
591
+
592
+
593
+ ASTRODATA_ARGS = ["fitsimage", "data", "Tb", "sigma", "center", "restfreq",
594
+ "cfactor", "bunit", "fitsimage_org", "sigma_org",
595
+ "beam_org", "fitsheader", "pv", "pvpa"]
596
+
597
+
578
598
  @dataclass
579
599
  class AstroFrame():
580
600
  """Parameter set to limit and reshape the data in the AstroData format.
@@ -666,6 +686,135 @@ class AstroFrame():
666
686
  x[i], y[i] = rel2abs(*p, self.Xlim, self.Ylim)
667
687
  return np.array([x, y])
668
688
 
689
+ def _get_restfreq(self, header: dict):
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
+ if "RESTFRQ" in header:
694
+ return header["RESTFRQ"]
695
+ if "RESTFREQ" in header:
696
+ return header["RESTFREQ"]
697
+ return None
698
+
699
+ def _read_fitsimage(self, d: AstroData, i: int, grid: list) -> list:
700
+ """Read FITS-derived values into d and return the FITS grid."""
701
+ if d.fitsimage[i] is None:
702
+ return grid
703
+
704
+ fd = FitsData(d.fitsimage[i])
705
+ if d.fitsheader[i] is None:
706
+ d.fitsheader[i] = fd.get_header()
707
+ if d.center[i] is None and not self.pv:
708
+ d.center[i] = fd.get_center()
709
+ if d.restfreq[i] is None:
710
+ d.restfreq[i] = self._get_restfreq(d.fitsheader[i])
711
+ d.data[i] = fd.get_data()
712
+ grid = fd.get_grid(center=d.center[i], dist=self.dist,
713
+ restfreq=d.restfreq[i], vsys=self.vsys,
714
+ pv=self.pv)
715
+ if fd.wcsrot:
716
+ d.center[i] = fd.get_center()
717
+ d.beam[i] = fd.get_beam(dist=self.dist)
718
+ d.bunit[i] = fd.get_header("BUNIT")
719
+ return grid
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
+ def _shift_center(self, d: AstroData, i: int, grid: list) -> list:
728
+ if self._no_newcenter(d.center[i]):
729
+ return grid
730
+
731
+ cx, cy = coord2xy(d.center[i], self.center) * 3600
732
+ grid[0] = grid[0] + cx # Don't use += cx.
733
+ grid[1] = grid[1] + cy # Don't use += cy.
734
+ d.center[i] = self.center
735
+ return grid
736
+
737
+ def _ascending_v(self, d: AstroData, i: int, v: np.ndarray | None):
738
+ if v is not None and len(v) > 1 and v[1] < v[0]:
739
+ d.data[i], v = d.data[i][::-1], v[::-1]
740
+ print('Velocity has been inverted.')
741
+ d.v = v
742
+
743
+ def _xyskip(self, d: AstroData, i: int,
744
+ x: np.ndarray | None, y: np.ndarray | None):
745
+ d.x = x[::self.xskip]
746
+ d.y = y[::self.yskip]
747
+ data = np.moveaxis(d.data[i], [-2, -1], [0, 1])
748
+ data = data[::self.yskip, ::self.xskip]
749
+ d.data[i] = np.moveaxis(data, [0, 1], [-2, -1])
750
+
751
+ def _trim_skip(self, d: AstroData, i: int, grid: list):
752
+ d.data[i], grid = trim(data=d.data[i],
753
+ x=grid[0], y=grid[1], v=grid[2],
754
+ xlim=self.xlim, ylim=self.ylim,
755
+ vlim=self.vlim, pv=self.pv)
756
+ self._ascending_v(d, i, v=grid[2])
757
+ grid = [grid[0], d.v] if self.pv else [grid[0], grid[1]]
758
+ if self.swapxy:
759
+ grid = [grid[1], grid[0]]
760
+ d.data[i] = np.moveaxis(d.data[i], 1, 0)
761
+ self._xyskip(d, i, x=grid[0], y=grid[1])
762
+ for axis in ['x', 'y', 'v']:
763
+ setattr(d, f"d{axis}", _get_gridsep(getattr(d, axis)))
764
+
765
+ def _convert_to_Tb(self, d: AstroData, i: int):
766
+ """Convert Jy/beam data to brightness temperature if requested."""
767
+ if not d.Tb[i]:
768
+ return
769
+
770
+ dx = d.dy if self.swapxy else d.dx
771
+ header = {"CDELT1": dx / 3600,
772
+ "CUNIT1": "DEG",
773
+ "RESTFREQ": d.restfreq[i]}
774
+ if None not in d.beam[i]:
775
+ header["BMAJ"] = d.beam[i][0] / 3600 / self.dist
776
+ header["BMIN"] = d.beam[i][1] / 3600 / self.dist
777
+ factor = Jy2K(header=header)
778
+ d.data[i] = d.data[i] * factor
779
+ if d.sigma[i] is not None:
780
+ d.sigma[i] = d.sigma[i] * factor
781
+
782
+ def _set_pv_beam(self, d: AstroData, i: int):
783
+ """Set effective PV beam."""
784
+ if not self.pv or d.pv[i] or None in d.beam[i]:
785
+ return
786
+
787
+ bmaj, bmin, bpa = d.beam_org[i] = d.beam[i]
788
+ if d.pvpa[i] is None:
789
+ d.pvpa[i] = bpa
790
+ print("pvpa is not specified. pvpa=bpa is assumed.")
791
+ angle = np.radians(bpa - d.pvpa[i])
792
+ beam_incut = 1 / np.hypot(np.cos(angle) / bmaj, np.sin(angle) / bmin)
793
+ d.beam[i] = np.array([np.abs(d.dv), beam_incut, 0])
794
+
795
+ def _read_one(self, d: AstroData, i: int):
796
+ if d.center[i] == 'common':
797
+ d.center[i] = self.center
798
+ d.sigma_org[i] = d.sigma[i]
799
+ grid = self._read_fitsimage(d, i, grid=[d.x, d.y, d.v])
800
+ if d.data[i] is not None:
801
+ d.sigma[i] = estimate_rms(d.data[i], d.sigma[i])
802
+ grid = self._shift_center(d, i, grid)
803
+ self._trim_skip(d, i, grid)
804
+ if self.quadrants is not None:
805
+ d.data[i], d.x, d.y \
806
+ = quadrantmean(d.data[i], d.x, d.y, self.quadrants)
807
+ d.data[i] = d.data[i] * d.cfactor[i]
808
+ if d.sigma[i] is not None:
809
+ d.sigma[i] = d.sigma[i] * d.cfactor[i]
810
+ self._convert_to_Tb(d, i)
811
+ self._set_pv_beam(d, i)
812
+ d.pv[i] = self.pv
813
+ d.Tb[i] = False
814
+ d.cfactor[i] = 1
815
+ d.fitsimage_org[i] = d.fitsimage[i]
816
+ d.fitsimage[i] = None
817
+
669
818
  def read(self, d: AstroData, xskip: int = 1, yskip: int = 1):
670
819
  """Get data, grid, sigma, beam, and bunit from AstroData, which is a part of the input of add_color, add_contour, add_segment, and add_rgb.
671
820
 
@@ -673,145 +822,12 @@ class AstroFrame():
673
822
  d (AstroData): Dataclass for the add_* input.
674
823
  xskip, yskip (int): Spatial pixel skip. Defaults to 1.
675
824
  """
676
- if type(d.fitsimage) is not list:
677
- d.fitsimage = [d.fitsimage] * d.n
678
- if type(d.data) is not list:
679
- d.data = [d.data] * d.n
680
- if np.ndim(d.beam) == 1:
681
- d.beam = [d.beam] * d.n
682
- if type(d.Tb) is not list:
683
- d.Tb = [d.Tb] * d.n
684
- if type(d.sigma) is not list:
685
- d.sigma = [d.sigma] * d.n
686
- if type(d.center) is not list:
687
- d.center = [d.center] * d.n
688
- if type(d.restfreq) is not list:
689
- d.restfreq = [d.restfreq] * d.n
690
- if type(d.cfactor) is not list:
691
- d.cfactor = [d.cfactor] * d.n
692
- if type(d.bunit) is not list:
693
- d.bunit = [d.bunit] * d.n
694
- if type(d.fitsimage_org) is not list:
695
- d.fitsimage_org = [d.fitsimage_org] * d.n
696
- if type(d.sigma_org) is not list:
697
- d.sigma_org = [d.sigma_org] * d.n
698
- if type(d.beam_org) is not list:
699
- d.beam_org = [d.beam_org] * d.n
700
- if type(d.fitsheader) is not list:
701
- d.fitsheader = [d.fitsheader] * d.n
702
- if type(d.pv) is not list:
703
- d.pv = [d.pv] * d.n
704
- if type(d.pvpa) is not list:
705
- d.pvpa = [d.pvpa] * d.n
706
- grid0 = [d.x, d.y, d.v]
825
+ self.xskip = xskip
826
+ self.yskip = yskip
827
+ for name in ASTRODATA_ARGS:
828
+ setattr(d, name, _as_list(getattr(d, name), d.n))
829
+ d.beam = _as_list(d.beam, d.n, isbeam=True)
707
830
  for i in range(d.n):
708
- if d.center[i] == 'common':
709
- d.center[i] = self.center
710
- grid = grid0.copy()
711
- if d.fitsimage[i] is not None:
712
- fd = FitsData(d.fitsimage[i])
713
- if d.fitsheader[i] is None:
714
- d.fitsheader[i] = fd.get_header()
715
- if d.center[i] is None and not self.pv:
716
- d.center[i] = fd.get_center()
717
- if d.restfreq[i] is None:
718
- h = d.fitsheader[i]
719
- if 'NAXIS3' in h and h['NAXIS3'] == 1 and not self.pv:
720
- d.restfreq[i] = h['CRVAL3']
721
- elif 'RESTFRQ' in h:
722
- d.restfreq[i] = h['RESTFRQ']
723
- elif 'RESTFREQ' in h:
724
- d.restfreq[i] = h['RESTFREQ']
725
- d.data[i] = fd.get_data()
726
- grid = fd.get_grid(center=d.center[i], dist=self.dist,
727
- restfreq=d.restfreq[i], vsys=self.vsys,
728
- pv=self.pv)
729
- if fd.wcsrot:
730
- d.center[i] = fd.get_center() # for WCS rotation
731
- d.beam[i] = fd.get_beam(dist=self.dist)
732
- d.bunit[i] = fd.get_header('BUNIT')
733
- if d.data[i] is not None:
734
- d.sigma_org[i] = d.sigma[i]
735
- d.sigma[i] = estimate_rms(d.data[i], d.sigma[i])
736
- diffcent = (not self.pv
737
- and self.center is not None
738
- and d.center[i] is not None
739
- and d.center[i] != self.center)
740
- if diffcent:
741
- cx, cy = coord2xy(d.center[i], self.center) * 3600
742
- grid[0] = grid[0] + cx # Don't use += cx.
743
- grid[1] = grid[1] + cy # Don't use += cy.
744
- d.center[i] = self.center
745
- d.data[i], grid = trim(data=d.data[i],
746
- x=grid[0], y=grid[1], v=grid[2],
747
- xlim=self.xlim, ylim=self.ylim,
748
- vlim=self.vlim, pv=self.pv)
749
- v = grid[2]
750
- has_v = v is not None and len(v) > 1
751
- if has_v and v[1] < v[0]:
752
- d.data[i], v = d.data[i][::-1], v[::-1]
753
- print('Velocity has been inverted.')
754
- d.v = v
755
- d.dv = v[1] - v[0] if has_v else None
756
- grid = grid[:3:2] if self.pv else grid[:2]
757
- if self.swapxy:
758
- grid = [grid[1], grid[0]]
759
- d.data[i] = np.moveaxis(d.data[i], 1, 0)
760
- grid[0] = grid[0][::xskip]
761
- grid[1] = grid[1][::yskip]
762
- a = d.data[i]
763
- a = np.moveaxis(a, [-2, -1], [0, 1])
764
- a = a[::yskip, ::xskip]
765
- a = np.moveaxis(a, [0, 1], [-2, -1])
766
- d.data[i] = a
767
- x, y = d.x, d.y = grid
768
- has_x = x is not None and len(x) > 1
769
- d.dx = x[1] - x[0] if has_x else None
770
- has_y = y is not None and len(y) > 1
771
- d.dy = y[1] - y[0] if has_y else None
772
- if self.quadrants is not None:
773
- d.data[i], d.x, d.y \
774
- = quadrantmean(d.data[i], d.x, d.y, self.quadrants)
775
- d.data[i] = d.data[i] * d.cfactor[i]
776
- if d.sigma[i] is not None:
777
- d.sigma[i] = d.sigma[i] * d.cfactor[i]
778
- if d.Tb[i]:
779
- dx = d.dy if self.swapxy else d.dx
780
- header = {'CDELT1': dx / 3600,
781
- 'CUNIT1': 'DEG',
782
- 'RESTFREQ': d.restfreq[i]}
783
- if None not in d.beam[i]:
784
- header['BMAJ'] = d.beam[i][0] / 3600 / self.dist
785
- header['BMIN'] = d.beam[i][1] / 3600 / self.dist
786
- d.data[i] = d.data[i] * Jy2K(header=header)
787
- d.sigma[i] = d.sigma[i] * Jy2K(header=header)
788
- if self.pv and not d.pv[i] and None not in d.beam[i]:
789
- bmaj, bmin, bpa = d.beam_org[i] = d.beam[i]
790
- if d.pvpa[i] is None:
791
- d.pvpa[i] = bpa
792
- print('pvpa is not specified. pvpa=bpa is assumed.')
793
- p = np.radians(bpa - d.pvpa[i])
794
- b = 1 / np.hypot(np.cos(p) / bmaj, np.sin(p) / bmin)
795
- d.beam[i] = np.array([np.abs(d.dv), b, 0])
796
- d.pv[i] = self.pv
797
- d.Tb[i] = False
798
- d.cfactor[i] = 1
799
- if d.fitsimage[i] is not None:
800
- d.fitsimage_org[i] = d.fitsimage[i]
801
- d.fitsimage[i] = None
802
- if d.n == 1:
803
- d.data = d.data[0]
804
- d.beam = d.beam[0]
805
- d.fitsimage = d.fitsimage[0]
806
- d.Tb = d.Tb[0]
807
- d.sigma = d.sigma[0]
808
- d.center = d.center[0]
809
- d.restfreq = d.restfreq[0]
810
- d.cfactor = d.cfactor[0]
811
- d.bunit = d.bunit[0]
812
- d.fitsimage_org = d.fitsimage_org[0]
813
- d.sigma_org = d.sigma_org[0]
814
- d.beam_org = d.beam_org[0]
815
- d.fitsheader = d.fitsheader[0]
816
- d.pv = d.pv[0]
817
- d.pvpa = d.pvpa[0]
831
+ self._read_one(d, i)
832
+ for name in ASTRODATA_ARGS + ["beam"]:
833
+ setattr(d, name, _scalar_if_single(getattr(d, name), d.n))
@@ -29,7 +29,7 @@ def Jy2K(header=None, bmaj: float | None = None, bmin: float | None = None,
29
29
  else:
30
30
  print('Use CDELT1^2 for Tb conversion.')
31
31
  todiameter = np.sqrt(4 * np.log(2) / np.pi) * 3600
32
- bmaj = bmin = header['CDELT1'] * todiameter
32
+ bmaj = bmin = np.abs(header['CDELT1']) * todiameter
33
33
  if header['CUNIT1'] == 'arcsec':
34
34
  bmaj, bmin = bmaj / 3600, bmin / 3600
35
35
  if 'RESTFREQ' in header:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: plotastrodata
3
- Version: 1.9.6
3
+ Version: 1.9.7
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