plotastrodata 1.9.6__tar.gz → 1.9.8__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {plotastrodata-1.9.6/plotastrodata.egg-info → plotastrodata-1.9.8}/PKG-INFO +1 -1
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/__init__.py +1 -1
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/analysis_utils.py +155 -141
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/fits_utils.py +145 -140
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/plot_utils.py +1 -1
- {plotastrodata-1.9.6 → plotastrodata-1.9.8/plotastrodata.egg-info}/PKG-INFO +1 -1
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/LICENSE +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/MANIFEST.in +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/README.md +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/const_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/coord_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/ext_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/fft_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/fitting_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/los_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/matrix_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/noise_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata/other_utils.py +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata.egg-info/SOURCES.txt +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata.egg-info/dependency_links.txt +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata.egg-info/not-zip-safe +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata.egg-info/requires.txt +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/plotastrodata.egg-info/top_level.txt +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/setup.cfg +0 -0
- {plotastrodata-1.9.6 → plotastrodata-1.9.8}/setup.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.8
|
|
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
|
|
@@ -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,133 @@ 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 "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"]
|
|
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 _shift_center(self, d: AstroData, i: int, grid: list) -> list:
|
|
722
|
+
corg = d.center[i]
|
|
723
|
+
cnew = self.center
|
|
724
|
+
if self.pv or cnew is None or corg is None or corg == cnew:
|
|
725
|
+
return grid
|
|
726
|
+
|
|
727
|
+
cx, cy = coord2xy(corg, cnew) * 3600
|
|
728
|
+
grid[0] = grid[0] + cx # Don't use += cx.
|
|
729
|
+
grid[1] = grid[1] + cy # Don't use += cy.
|
|
730
|
+
d.center[i] = cnew
|
|
731
|
+
return grid
|
|
732
|
+
|
|
733
|
+
def _ascending_v(self, d: AstroData, i: int, v: np.ndarray | None):
|
|
734
|
+
if v is not None and len(v) > 1 and v[1] < v[0]:
|
|
735
|
+
d.data[i], v = d.data[i][::-1], v[::-1]
|
|
736
|
+
print('Velocity has been inverted.')
|
|
737
|
+
d.v = v
|
|
738
|
+
|
|
739
|
+
def _xyskip(self, d: AstroData, i: int,
|
|
740
|
+
x: np.ndarray | None, y: np.ndarray | None):
|
|
741
|
+
d.x = x[::self.xskip]
|
|
742
|
+
d.y = y[::self.yskip]
|
|
743
|
+
data = np.moveaxis(d.data[i], [-2, -1], [0, 1])
|
|
744
|
+
data = data[::self.yskip, ::self.xskip]
|
|
745
|
+
d.data[i] = np.moveaxis(data, [0, 1], [-2, -1])
|
|
746
|
+
|
|
747
|
+
def _trim_skip(self, d: AstroData, i: int, grid: list):
|
|
748
|
+
d.data[i], grid = trim(data=d.data[i],
|
|
749
|
+
x=grid[0], y=grid[1], v=grid[2],
|
|
750
|
+
xlim=self.xlim, ylim=self.ylim,
|
|
751
|
+
vlim=self.vlim, pv=self.pv)
|
|
752
|
+
self._ascending_v(d, i, v=grid[2])
|
|
753
|
+
grid = [grid[0], d.v] if self.pv else [grid[0], grid[1]]
|
|
754
|
+
if self.swapxy:
|
|
755
|
+
grid.reverse()
|
|
756
|
+
d.data[i] = np.moveaxis(d.data[i], 1, 0)
|
|
757
|
+
self._xyskip(d, i, x=grid[0], y=grid[1])
|
|
758
|
+
if self.pv:
|
|
759
|
+
d.v = d.y
|
|
760
|
+
for axis in ['x', 'y', 'v']:
|
|
761
|
+
setattr(d, f"d{axis}", _get_gridsep(getattr(d, axis)))
|
|
762
|
+
|
|
763
|
+
def _convert_to_Tb(self, d: AstroData, i: int):
|
|
764
|
+
"""Convert Jy/beam data to brightness temperature if requested."""
|
|
765
|
+
if not d.Tb[i]:
|
|
766
|
+
return
|
|
767
|
+
|
|
768
|
+
dx = d.dy if self.swapxy else d.dx
|
|
769
|
+
header = {"CDELT1": dx / 3600,
|
|
770
|
+
"CUNIT1": "DEG",
|
|
771
|
+
"RESTFREQ": d.restfreq[i]}
|
|
772
|
+
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
|
|
775
|
+
factor = Jy2K(header=header)
|
|
776
|
+
d.data[i] = d.data[i] * factor
|
|
777
|
+
if d.sigma[i] is not None:
|
|
778
|
+
d.sigma[i] = d.sigma[i] * factor
|
|
779
|
+
|
|
780
|
+
def _set_pv_beam(self, d: AstroData, i: int):
|
|
781
|
+
"""Set effective PV beam."""
|
|
782
|
+
if not self.pv or d.pv[i] or None in d.beam[i]:
|
|
783
|
+
return
|
|
784
|
+
|
|
785
|
+
bmaj, bmin, bpa = d.beam_org[i] = d.beam[i]
|
|
786
|
+
if d.pvpa[i] is None:
|
|
787
|
+
d.pvpa[i] = bpa
|
|
788
|
+
print("pvpa is not specified. pvpa=bpa is assumed.")
|
|
789
|
+
angle = np.radians(bpa - d.pvpa[i])
|
|
790
|
+
beam_incut = 1 / np.hypot(np.cos(angle) / bmaj, np.sin(angle) / bmin)
|
|
791
|
+
d.beam[i] = np.array([np.abs(d.dv), beam_incut, 0])
|
|
792
|
+
|
|
793
|
+
def _read_one(self, d: AstroData, i: int):
|
|
794
|
+
if d.center[i] == 'common':
|
|
795
|
+
d.center[i] = self.center
|
|
796
|
+
d.sigma_org[i] = d.sigma[i]
|
|
797
|
+
grid = self._read_fitsimage(d, i, grid=[d.x, d.y, d.v])
|
|
798
|
+
if d.data[i] is not None:
|
|
799
|
+
d.sigma[i] = estimate_rms(d.data[i], d.sigma[i])
|
|
800
|
+
grid = self._shift_center(d, i, grid)
|
|
801
|
+
self._trim_skip(d, i, grid)
|
|
802
|
+
if self.quadrants is not None:
|
|
803
|
+
d.data[i], d.x, d.y \
|
|
804
|
+
= quadrantmean(d.data[i], d.x, d.y, self.quadrants)
|
|
805
|
+
d.data[i] = d.data[i] * d.cfactor[i]
|
|
806
|
+
if d.sigma[i] is not None:
|
|
807
|
+
d.sigma[i] = d.sigma[i] * d.cfactor[i]
|
|
808
|
+
self._convert_to_Tb(d, i)
|
|
809
|
+
self._set_pv_beam(d, i)
|
|
810
|
+
d.pv[i] = self.pv
|
|
811
|
+
d.Tb[i] = False
|
|
812
|
+
d.cfactor[i] = 1
|
|
813
|
+
d.fitsimage_org[i] = d.fitsimage[i]
|
|
814
|
+
d.fitsimage[i] = None
|
|
815
|
+
|
|
669
816
|
def read(self, d: AstroData, xskip: int = 1, yskip: int = 1):
|
|
670
817
|
"""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
818
|
|
|
@@ -673,145 +820,12 @@ class AstroFrame():
|
|
|
673
820
|
d (AstroData): Dataclass for the add_* input.
|
|
674
821
|
xskip, yskip (int): Spatial pixel skip. Defaults to 1.
|
|
675
822
|
"""
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
d
|
|
680
|
-
|
|
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]
|
|
823
|
+
self.xskip = xskip
|
|
824
|
+
self.yskip = yskip
|
|
825
|
+
for name in ASTRODATA_ARGS:
|
|
826
|
+
setattr(d, name, _as_list(getattr(d, name), d.n))
|
|
827
|
+
d.beam = _as_list(d.beam, d.n, isbeam=True)
|
|
707
828
|
for i in range(d.n):
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
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]
|
|
829
|
+
self._read_one(d, i)
|
|
830
|
+
for name in ASTRODATA_ARGS + ["beam"]:
|
|
831
|
+
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:
|
|
@@ -177,6 +177,137 @@ class FitsData:
|
|
|
177
177
|
self.gen_data(**kwargs)
|
|
178
178
|
return self.data
|
|
179
179
|
|
|
180
|
+
def _read_cd(self):
|
|
181
|
+
h = self.header
|
|
182
|
+
cdij = ['CD1_1', 'CD1_2', 'CD2_1', 'CD2_2']
|
|
183
|
+
if not np.all([k in list(h.keys()) for k in cdij]):
|
|
184
|
+
self.wcsrot = False
|
|
185
|
+
self.Mcd = None
|
|
186
|
+
return
|
|
187
|
+
|
|
188
|
+
self.wcsrot = True
|
|
189
|
+
cd11, cd12, cd21, cd22 = [h[k] for k in cdij]
|
|
190
|
+
self.Mcd = [[cd11, cd12], [cd21, cd22]]
|
|
191
|
+
if cd21 == 0:
|
|
192
|
+
rho_a = 0
|
|
193
|
+
else:
|
|
194
|
+
rho_a = np.arctan2(np.abs(cd21), np.sign(cd21) * cd11)
|
|
195
|
+
if cd12 == 0:
|
|
196
|
+
rho_b = 0
|
|
197
|
+
else:
|
|
198
|
+
rho_b = np.arctan2(np.abs(cd12), -np.sign(cd12) * cd22)
|
|
199
|
+
if (drho := np.abs(np.degrees(rho_a - rho_b))) > 1.0:
|
|
200
|
+
print('Angles from (CD21, CD11) and (CD12, CD22)'
|
|
201
|
+
+ f' are different by {drho:.2} degrees.')
|
|
202
|
+
crota2 = (rho_a + rho_b) / 2.
|
|
203
|
+
sin_rho = np.sin(crota2)
|
|
204
|
+
cos_rho = np.cos(crota2)
|
|
205
|
+
cdelt1 = cd11 * cos_rho + cd21 * sin_rho
|
|
206
|
+
cdelt2 = -cd12 * sin_rho + cd22 * cos_rho
|
|
207
|
+
crota2 = np.degrees(crota2)
|
|
208
|
+
h['CDELT1'] = cdelt1
|
|
209
|
+
h['CDELT2'] = cdelt2
|
|
210
|
+
for k in cdij:
|
|
211
|
+
del h[k]
|
|
212
|
+
print(f'WCS rotation was found (CROTA2 = {crota2:f} deg).')
|
|
213
|
+
|
|
214
|
+
def _rotate_cd(self):
|
|
215
|
+
h = self.header
|
|
216
|
+
data = self.get_data()
|
|
217
|
+
ic = len(self.x) // 2
|
|
218
|
+
jc = len(self.y) // 2
|
|
219
|
+
h['CRPIX1'] = ic + 1
|
|
220
|
+
h['CRPIX2'] = jc + 1
|
|
221
|
+
xc = self.x[ic] / self.dx
|
|
222
|
+
yc = self.y[jc] / self.dy
|
|
223
|
+
xc, yc = dot2d(self.Mcd, [xc, yc])
|
|
224
|
+
newcenter = xy2coord([xc, yc], coordorg=self.get_center())
|
|
225
|
+
xc, yc = coord2xy(coords=newcenter)
|
|
226
|
+
h['CRVAL1'] = xc
|
|
227
|
+
h['CRVAL2'] = yc
|
|
228
|
+
self.x = self.x - self.x[ic]
|
|
229
|
+
self.y = self.y - self.y[jc]
|
|
230
|
+
x = self.x / (3600 if isdeg(h['CUNIT1']) else 1)
|
|
231
|
+
y = self.y / (3600 if isdeg(h['CUNIT2']) else 1)
|
|
232
|
+
X, Y = np.meshgrid(x, y)
|
|
233
|
+
Mcdinv = np.linalg.inv(self.Mcd)
|
|
234
|
+
xnew, ynew = dot2d(Mcdinv, [X, Y])
|
|
235
|
+
datanew = RGIxy(self.y / self.dy, self.x / self.dx,
|
|
236
|
+
data, (ynew, xnew))
|
|
237
|
+
self.data = datanew
|
|
238
|
+
print('Data values were interpolated for WCS rotation.')
|
|
239
|
+
|
|
240
|
+
def _get_genx_geny(self, center: str, dist: float):
|
|
241
|
+
h = self.header
|
|
242
|
+
cxy = (0, 0)
|
|
243
|
+
if center is not None and not self.wcsrot:
|
|
244
|
+
coordorg = xy2coord([h["CRVAL1"], h["CRVAL2"]])
|
|
245
|
+
if (radesys := h.get("RADESYS")) is not None:
|
|
246
|
+
coordorg = f"{radesys} {coordorg}"
|
|
247
|
+
cxy = coord2xy(center, coordorg)
|
|
248
|
+
slabel = ["x", "y"]
|
|
249
|
+
|
|
250
|
+
def wrapper(i: int):
|
|
251
|
+
def gen_s(s_in: np.ndarray | None) -> None:
|
|
252
|
+
if h.get(f"NAXIS{i+1}") is None or s_in is None:
|
|
253
|
+
s, ds = None, None
|
|
254
|
+
else:
|
|
255
|
+
s = (s_in - cxy[i]) * dist
|
|
256
|
+
if isdeg(h[f"CUNIT{i+1}"]):
|
|
257
|
+
s *= 3600.
|
|
258
|
+
ds = None if len(s) == 0 else s[1] - s[0]
|
|
259
|
+
setattr(self, f"{slabel[i]}", s)
|
|
260
|
+
setattr(self, f"d{slabel[i]}", ds)
|
|
261
|
+
return gen_s
|
|
262
|
+
|
|
263
|
+
return wrapper(0), wrapper(1)
|
|
264
|
+
|
|
265
|
+
def _get_genv(self, restfreq: float | None, vsys: float, pv: bool):
|
|
266
|
+
h = self.header
|
|
267
|
+
|
|
268
|
+
def gen_v(v_in: np.ndarray) -> None:
|
|
269
|
+
vaxis = "2" if pv else "3"
|
|
270
|
+
if h.get(f"NAXIS{vaxis}") is None or v_in is None:
|
|
271
|
+
self.v, self.dv = None, None
|
|
272
|
+
return
|
|
273
|
+
|
|
274
|
+
if restfreq is None:
|
|
275
|
+
freq = np.mean(v_in)
|
|
276
|
+
print("restfreq is assumed to be the center.")
|
|
277
|
+
else:
|
|
278
|
+
freq = restfreq
|
|
279
|
+
v = v_in + h[f"CRVAL{vaxis}"]
|
|
280
|
+
key = f'CUNIT{vaxis}'
|
|
281
|
+
cunitv = h[key].strip()
|
|
282
|
+
match cunitv:
|
|
283
|
+
case "Hz" | "HZ":
|
|
284
|
+
if freq == 0:
|
|
285
|
+
print("v is read as is, because restfreq=0.")
|
|
286
|
+
else:
|
|
287
|
+
v = (1 - v / freq) * cu.c_kms - vsys
|
|
288
|
+
case "m/s" | "M/S":
|
|
289
|
+
print(f'{key}={cunitv} found.')
|
|
290
|
+
v = v * 1e-3 - vsys
|
|
291
|
+
case 'km/s' | "KM/S":
|
|
292
|
+
print(f'{key}={cunitv} found.')
|
|
293
|
+
v = v - vsys
|
|
294
|
+
case _:
|
|
295
|
+
print(f'Unknown CUNIT3 {cunitv} found.'
|
|
296
|
+
+ ' v is read as is.')
|
|
297
|
+
dv = None if len(v) == 0 else v[1] - v[0]
|
|
298
|
+
self.v, self.dv = v, dv
|
|
299
|
+
|
|
300
|
+
return gen_v
|
|
301
|
+
|
|
302
|
+
def _get_array(self, i: int) -> np.ndarray:
|
|
303
|
+
h = self.header
|
|
304
|
+
n = h.get(f"NAXIS{i:d}")
|
|
305
|
+
if n is None:
|
|
306
|
+
return None
|
|
307
|
+
|
|
308
|
+
s = (np.arange(n) - h[f"CRPIX{i:d}"] + 1) * h[f"CDELT{i:d}"]
|
|
309
|
+
return s
|
|
310
|
+
|
|
180
311
|
def gen_grid(self, center: str | None = None, dist: float = 1.,
|
|
181
312
|
restfreq: float | None = None, vsys: float = 0.,
|
|
182
313
|
pv: bool = False) -> None:
|
|
@@ -191,146 +322,20 @@ class FitsData:
|
|
|
191
322
|
"""
|
|
192
323
|
h = self.get_header()
|
|
193
324
|
# WCS rotation (Calabretta & Greisen 2002, Astronomy & Astrophysics, 395, 1077)
|
|
194
|
-
self.
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
rho_a = np.arctan2(np.abs(cd21), np.sign(cd21) * cd11)
|
|
203
|
-
if cd12 == 0:
|
|
204
|
-
rho_b = 0
|
|
205
|
-
else:
|
|
206
|
-
rho_b = np.arctan2(np.abs(cd12), -np.sign(cd12) * cd22)
|
|
207
|
-
if (drho := np.abs(np.degrees(rho_a - rho_b))) > 1.0:
|
|
208
|
-
print('Angles from (CD21, CD11) and (CD12, CD22)'
|
|
209
|
-
+ f' are different by {drho:.2} degrees.')
|
|
210
|
-
crota2 = (rho_a + rho_b) / 2.
|
|
211
|
-
sin_rho = np.sin(crota2)
|
|
212
|
-
cos_rho = np.cos(crota2)
|
|
213
|
-
cdelt1 = cd11 * cos_rho + cd21 * sin_rho
|
|
214
|
-
cdelt2 = -cd12 * sin_rho + cd22 * cos_rho
|
|
215
|
-
crota2 = np.degrees(crota2)
|
|
216
|
-
h['CDELT1'] = cdelt1
|
|
217
|
-
h['CDELT2'] = cdelt2
|
|
218
|
-
del h['CD1_1']
|
|
219
|
-
del h['CD1_2']
|
|
220
|
-
del h['CD2_1']
|
|
221
|
-
del h['CD2_2']
|
|
222
|
-
print(f'WCS rotation was found (CROTA2 = {crota2:f} deg).')
|
|
223
|
-
# spatial center
|
|
224
|
-
if center is None or self.wcsrot:
|
|
225
|
-
cx, cy = 0, 0
|
|
325
|
+
self._read_cd()
|
|
326
|
+
gen_x, gen_y = self._get_genx_geny(center, dist)
|
|
327
|
+
restfreq = restfreq or h.get('RESTFRQ') or h.get('RESTFREQ')
|
|
328
|
+
gen_v = self._get_genv(restfreq, vsys, pv)
|
|
329
|
+
if pv:
|
|
330
|
+
gen_x(self._get_array(1))
|
|
331
|
+
gen_v(self._get_array(2))
|
|
332
|
+
self.y, self.dy = None, None
|
|
226
333
|
else:
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
c0 = f'{radesys} {c0}'
|
|
231
|
-
cx, cy = coord2xy(center, c0)
|
|
232
|
-
# rest frequency
|
|
233
|
-
if restfreq is None:
|
|
234
|
-
if 'RESTFRQ' in h:
|
|
235
|
-
restfreq = h['RESTFRQ']
|
|
236
|
-
if 'RESTFREQ' in h:
|
|
237
|
-
restfreq = h['RESTFREQ']
|
|
238
|
-
self.x, self.y, self.v = None, None, None
|
|
239
|
-
self.dx, self.dy, self.dv = None, None, None
|
|
240
|
-
|
|
241
|
-
def get_list(i: int, crval=False) -> np.ndarray:
|
|
242
|
-
s = np.arange(h[f'NAXIS{i:d}'])
|
|
243
|
-
s = (s - h[f'CRPIX{i:d}'] + 1) * h[f'CDELT{i:d}']
|
|
244
|
-
if crval:
|
|
245
|
-
s = s + h[f'CRVAL{i:d}']
|
|
246
|
-
return s
|
|
247
|
-
|
|
248
|
-
def gen_x(s_in: np.ndarray) -> None:
|
|
249
|
-
s = (s_in - cx) * dist
|
|
250
|
-
if isdeg(h['CUNIT1']):
|
|
251
|
-
s *= 3600.
|
|
252
|
-
self.x, self.dx = s, s[1] - s[0]
|
|
253
|
-
|
|
254
|
-
def gen_y(s_in: np.ndarray) -> None:
|
|
255
|
-
s = (s_in - cy) * dist
|
|
256
|
-
if isdeg(h['CUNIT2']):
|
|
257
|
-
s *= 3600.
|
|
258
|
-
self.y, self.dy = s, s[1] - s[0]
|
|
259
|
-
|
|
260
|
-
def gen_v(s_in: np.ndarray) -> None:
|
|
261
|
-
if restfreq is None:
|
|
262
|
-
freq = np.mean(s_in)
|
|
263
|
-
print('restfreq is assumed to be the center.')
|
|
264
|
-
else:
|
|
265
|
-
freq = restfreq
|
|
266
|
-
|
|
267
|
-
vaxis = '2' if pv else '3'
|
|
268
|
-
key = f'CUNIT{vaxis}'
|
|
269
|
-
cunitv = h[key]
|
|
270
|
-
match cunitv.strip():
|
|
271
|
-
case 'Hz':
|
|
272
|
-
if freq == 0:
|
|
273
|
-
print('v is frequency because restfreq=0.')
|
|
274
|
-
s = s_in * 1
|
|
275
|
-
else:
|
|
276
|
-
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
277
|
-
case 'HZ':
|
|
278
|
-
if freq == 0:
|
|
279
|
-
print('v is frequency because restfreq=0.')
|
|
280
|
-
s = s_in * 1
|
|
281
|
-
else:
|
|
282
|
-
s = (1 - s_in / freq) * cu.c_kms - vsys
|
|
283
|
-
case 'm/s':
|
|
284
|
-
print(f'{key}=\'m/s\' found.')
|
|
285
|
-
s = s_in * 1e-3 - vsys
|
|
286
|
-
case 'M/S':
|
|
287
|
-
print(f'{key}=\'M/S\' found.')
|
|
288
|
-
s = s_in * 1e-3 - vsys
|
|
289
|
-
case 'km/s':
|
|
290
|
-
print(f'{key}=\'km/s\' found.')
|
|
291
|
-
s = s_in - vsys
|
|
292
|
-
case 'KM/S':
|
|
293
|
-
print(f'{key}=\'KM/S\' found.')
|
|
294
|
-
s = s_in - vsys
|
|
295
|
-
case _:
|
|
296
|
-
print(f'Unknown CUNIT3 {cunitv} found.'
|
|
297
|
-
+ ' v is read as is.')
|
|
298
|
-
s = s_in - vsys
|
|
299
|
-
|
|
300
|
-
self.v, self.dv = s, s[1] - s[0]
|
|
301
|
-
|
|
302
|
-
if h['NAXIS'] > 0 and h['NAXIS1'] > 1:
|
|
303
|
-
gen_x(get_list(1))
|
|
304
|
-
if h['NAXIS'] > 1 and h['NAXIS2'] > 1:
|
|
305
|
-
gen_v(get_list(2, True)) if pv else gen_y(get_list(2))
|
|
306
|
-
if h['NAXIS'] > 2 and h['NAXIS3'] > 1:
|
|
307
|
-
gen_v(get_list(3, True))
|
|
308
|
-
|
|
334
|
+
gen_x(self._get_array(1))
|
|
335
|
+
gen_y(self._get_array(2))
|
|
336
|
+
gen_v(self._get_array(3))
|
|
309
337
|
if self.wcsrot:
|
|
310
|
-
|
|
311
|
-
self.header['CRPIX1'] = ic = len(self.x) // 2
|
|
312
|
-
self.header['CRPIX2'] = jc = len(self.y) // 2
|
|
313
|
-
xc = self.x[ic] / self.dx
|
|
314
|
-
yc = self.y[jc] / self.dy
|
|
315
|
-
Mcd = [[cd11, cd12], [cd21, cd22]]
|
|
316
|
-
xc, yc = dot2d(Mcd, [xc, yc])
|
|
317
|
-
newcenter = xy2coord(xy=[xc, yc],
|
|
318
|
-
coordorg=self.get_center())
|
|
319
|
-
xc, yc = coord2xy(coords=newcenter,
|
|
320
|
-
coordorg='00h00m00s 00d00m00s')
|
|
321
|
-
self.header['CRVAL1'] = xc
|
|
322
|
-
self.header['CRVAL2'] = yc
|
|
323
|
-
self.x = self.x - self.x[ic]
|
|
324
|
-
self.y = self.y - self.y[jc]
|
|
325
|
-
x = self.x / (3600 if isdeg(h['CUNIT1']) else 1)
|
|
326
|
-
y = self.y / (3600 if isdeg(h['CUNIT2']) else 1)
|
|
327
|
-
X, Y = np.meshgrid(x, y)
|
|
328
|
-
cdinv = np.linalg.inv([[cd11, cd12], [cd21, cd22]])
|
|
329
|
-
xnew, ynew = dot2d(cdinv, [X, Y])
|
|
330
|
-
datanew = RGIxy(self.y / self.dy, self.x / self.dx,
|
|
331
|
-
data, (ynew, xnew))
|
|
332
|
-
self.data = datanew
|
|
333
|
-
print('Data values were interpolated for WCS rotation.')
|
|
338
|
+
self._rotate_cd()
|
|
334
339
|
|
|
335
340
|
def get_grid(self, **kwargs) -> tuple[np.ndarray, np.ndarray, np.ndarray]:
|
|
336
341
|
"""Output the grids, [x, y, v]. This method can take the arguments of gen_grid().
|
|
@@ -338,7 +343,7 @@ class FitsData:
|
|
|
338
343
|
Returns:
|
|
339
344
|
tuple: (x, y, v).
|
|
340
345
|
"""
|
|
341
|
-
if not hasattr(self,
|
|
346
|
+
if not np.all([hasattr(self, s) for s in ["x", "y", "v"]]):
|
|
342
347
|
self.gen_grid(**kwargs)
|
|
343
348
|
return self.x, self.y, self.v
|
|
344
349
|
|
|
@@ -149,7 +149,7 @@ def _get_gridwidth(mode: str, rmax: float, cos_dec: float
|
|
|
149
149
|
def _get_v(p, v: np.ndarray | None = None,
|
|
150
150
|
restfreq: float | None = None,
|
|
151
151
|
vskip: int = 1) -> np.ndarray:
|
|
152
|
-
if p.fitsimage is not None:
|
|
152
|
+
if p.fitsimage is not None and v is None:
|
|
153
153
|
p.read(d := AstroData(fitsimage=p.fitsimage,
|
|
154
154
|
restfreq=restfreq, sigma=None))
|
|
155
155
|
v = d.v
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: plotastrodata
|
|
3
|
-
Version: 1.9.
|
|
3
|
+
Version: 1.9.8
|
|
4
4
|
Summary: plotastrodata is a tool for astronomers to create figures from FITS files and perform fundamental data analyses with ease.
|
|
5
5
|
Home-page: https://github.com/yusukeaso-astron/plotastrodata
|
|
6
6
|
Download-URL: https://github.com/yusukeaso-astron/plotastrodata
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|