plotastrodata 1.9.7__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.
Files changed (25) hide show
  1. {plotastrodata-1.9.7/plotastrodata.egg-info → plotastrodata-1.9.8}/PKG-INFO +1 -1
  2. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/__init__.py +1 -1
  3. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/analysis_utils.py +10 -12
  4. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/fits_utils.py +144 -139
  5. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/plot_utils.py +1 -1
  6. {plotastrodata-1.9.7 → plotastrodata-1.9.8/plotastrodata.egg-info}/PKG-INFO +1 -1
  7. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/LICENSE +0 -0
  8. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/MANIFEST.in +0 -0
  9. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/README.md +0 -0
  10. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/const_utils.py +0 -0
  11. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/coord_utils.py +0 -0
  12. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/ext_utils.py +0 -0
  13. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/fft_utils.py +0 -0
  14. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/fitting_utils.py +0 -0
  15. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/los_utils.py +0 -0
  16. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/matrix_utils.py +0 -0
  17. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/noise_utils.py +0 -0
  18. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata/other_utils.py +0 -0
  19. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata.egg-info/SOURCES.txt +0 -0
  20. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata.egg-info/dependency_links.txt +0 -0
  21. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata.egg-info/not-zip-safe +0 -0
  22. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata.egg-info/requires.txt +0 -0
  23. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/plotastrodata.egg-info/top_level.txt +0 -0
  24. {plotastrodata-1.9.7 → plotastrodata-1.9.8}/setup.cfg +0 -0
  25. {plotastrodata-1.9.7 → 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.7
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
@@ -1,4 +1,4 @@
1
1
  import warnings
2
2
 
3
3
  warnings.simplefilter('ignore', FutureWarning)
4
- __version__ = '1.9.7'
4
+ __version__ = '1.9.8'
@@ -688,12 +688,12 @@ class AstroFrame():
688
688
 
689
689
  def _get_restfreq(self, header: dict):
690
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
691
  if "RESTFRQ" in header:
694
692
  return header["RESTFRQ"]
695
693
  if "RESTFREQ" in header:
696
694
  return header["RESTFREQ"]
695
+ if "NAXIS3" in header and header["NAXIS3"] == 1 and not self.pv:
696
+ return header["CRVAL3"]
697
697
  return None
698
698
 
699
699
  def _read_fitsimage(self, d: AstroData, i: int, grid: list) -> list:
@@ -718,20 +718,16 @@ class AstroFrame():
718
718
  d.bunit[i] = fd.get_header("BUNIT")
719
719
  return grid
720
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
721
  def _shift_center(self, d: AstroData, i: int, grid: list) -> list:
728
- if self._no_newcenter(d.center[i]):
722
+ corg = d.center[i]
723
+ cnew = self.center
724
+ if self.pv or cnew is None or corg is None or corg == cnew:
729
725
  return grid
730
726
 
731
- cx, cy = coord2xy(d.center[i], self.center) * 3600
727
+ cx, cy = coord2xy(corg, cnew) * 3600
732
728
  grid[0] = grid[0] + cx # Don't use += cx.
733
729
  grid[1] = grid[1] + cy # Don't use += cy.
734
- d.center[i] = self.center
730
+ d.center[i] = cnew
735
731
  return grid
736
732
 
737
733
  def _ascending_v(self, d: AstroData, i: int, v: np.ndarray | None):
@@ -756,9 +752,11 @@ class AstroFrame():
756
752
  self._ascending_v(d, i, v=grid[2])
757
753
  grid = [grid[0], d.v] if self.pv else [grid[0], grid[1]]
758
754
  if self.swapxy:
759
- grid = [grid[1], grid[0]]
755
+ grid.reverse()
760
756
  d.data[i] = np.moveaxis(d.data[i], 1, 0)
761
757
  self._xyskip(d, i, x=grid[0], y=grid[1])
758
+ if self.pv:
759
+ d.v = d.y
762
760
  for axis in ['x', 'y', 'v']:
763
761
  setattr(d, f"d{axis}", _get_gridsep(getattr(d, axis)))
764
762
 
@@ -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.wcsrot = False
195
- cdij = ['CD1_1', 'CD1_2', 'CD2_1', 'CD2_2']
196
- if np.all([s in list(h.keys()) for s in cdij]):
197
- self.wcsrot = True
198
- cd11, cd12, cd21, cd22 = [h[s] for s in cdij]
199
- if cd21 == 0:
200
- rho_a = 0
201
- else:
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
- c0 = xy2coord([h['CRVAL1'], h['CRVAL2']])
228
- if 'RADESYS' in h:
229
- radesys = h['RADESYS']
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
- data = self.get_data()
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, 'x') or not hasattr(self, 'y'):
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.7
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