hyper-py-photometry 0.1.0__py3-none-any.whl
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.
- hyper_py/__init__.py +1 -0
- hyper_py/bkg_multigauss.py +524 -0
- hyper_py/bkg_single.py +477 -0
- hyper_py/config.py +43 -0
- hyper_py/create_background_slices.py +160 -0
- hyper_py/data_output.py +132 -0
- hyper_py/detection.py +142 -0
- hyper_py/extract_cubes.py +42 -0
- hyper_py/fitting.py +562 -0
- hyper_py/gaussfit.py +519 -0
- hyper_py/groups.py +66 -0
- hyper_py/hyper.py +150 -0
- hyper_py/logger.py +73 -0
- hyper_py/map_io.py +73 -0
- hyper_py/paths_io.py +122 -0
- hyper_py/photometry.py +114 -0
- hyper_py/run_hyper.py +45 -0
- hyper_py/single_map.py +716 -0
- hyper_py/survey.py +70 -0
- hyper_py/visualization.py +150 -0
- hyper_py_photometry-0.1.0.dist-info/METADATA +514 -0
- hyper_py_photometry-0.1.0.dist-info/RECORD +26 -0
- hyper_py_photometry-0.1.0.dist-info/WHEEL +5 -0
- hyper_py_photometry-0.1.0.dist-info/entry_points.txt +4 -0
- hyper_py_photometry-0.1.0.dist-info/licenses/LICENSE +13 -0
- hyper_py_photometry-0.1.0.dist-info/top_level.txt +1 -0
hyper_py/survey.py
ADDED
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
from astropy.io import fits
|
|
2
|
+
import numpy as np
|
|
3
|
+
from astropy import units as u
|
|
4
|
+
|
|
5
|
+
# Static beam info per survey_code (with conversion micron -> GHz)
|
|
6
|
+
|
|
7
|
+
BEAM_INFO = {
|
|
8
|
+
1: {'band': (70*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 5.2, 'area': None},
|
|
9
|
+
2: {'band': (100*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 6.8, 'area': None},
|
|
10
|
+
3: {'band': (160*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 12.0, 'area': None},
|
|
11
|
+
4: {'band': (250*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 18.0, 'area': 423},
|
|
12
|
+
5: {'band': (350*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 24.0, 'area': 751},
|
|
13
|
+
6: {'band': (500*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 34.5, 'area': 1587},
|
|
14
|
+
7: {'band': (450*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 8.0, 'area': None},
|
|
15
|
+
8: {'band': (850*u.micron).to(u.GHz, equivalencies=u.spectral()), 'beam': 14.5, 'area': None},
|
|
16
|
+
15: 'DYNAMIC',
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
arcsec2_to_sr = 2.35045e-11
|
|
21
|
+
|
|
22
|
+
def get_beam_info(survey_code, fits_file=None):
|
|
23
|
+
beam_arcsec = []
|
|
24
|
+
beam_area_arcsec2 = []
|
|
25
|
+
beam_area_sr = []
|
|
26
|
+
band_ref = []
|
|
27
|
+
|
|
28
|
+
entry = BEAM_INFO.get(survey_code)
|
|
29
|
+
|
|
30
|
+
if entry == 'DYNAMIC':
|
|
31
|
+
if not fits_file:
|
|
32
|
+
raise ValueError("FITS file required for ALMA Band 6 beam estimation.")
|
|
33
|
+
with fits.open(fits_file) as hdul:
|
|
34
|
+
hdr = hdul[0].header
|
|
35
|
+
bmin = hdr.get('BMIN', 0) * 3600 # degrees → arcsec
|
|
36
|
+
bmaj = hdr.get('BMAJ', 0) * 3600
|
|
37
|
+
beam = np.sqrt(bmin * bmaj)
|
|
38
|
+
|
|
39
|
+
beam_area_arcsec2 = 1.1331 * bmin * bmaj
|
|
40
|
+
beam_area_sr = beam_area_arcsec2 * arcsec2_to_sr
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# area = 1.133 * bmin * bmaj *(3600 * np.pi / 180) ** 2 / SR_TO_ARCSEC2
|
|
44
|
+
elif isinstance(entry, dict):
|
|
45
|
+
with fits.open(fits_file) as hdul: hdr = hdul[0].header
|
|
46
|
+
beam = np.mean(entry['beam']) if isinstance(entry['beam'], list) else entry['beam']
|
|
47
|
+
beam_area_arcsec2 = entry['area']
|
|
48
|
+
if beam_area_arcsec2 is None:
|
|
49
|
+
beam_area_arcsec2 = 1.1331 * beam ** 2
|
|
50
|
+
beam_area_sr = beam_area_arcsec2 * arcsec2_to_sr
|
|
51
|
+
else:
|
|
52
|
+
raise ValueError(f"Survey code {survey_code} not recognized.")
|
|
53
|
+
|
|
54
|
+
beam_arcsec = beam
|
|
55
|
+
beam_area_arcsec2 = beam_area_arcsec2
|
|
56
|
+
beam_area_sr = beam_area_sr
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
# --- Search for frequency axis (CTYPEi == 'FREQ') --- #
|
|
60
|
+
band_ref = None
|
|
61
|
+
|
|
62
|
+
for i in range(1, 6): # Loop over axis 1 to 5 in case of multi-dimensional FITS
|
|
63
|
+
ctype = hdr.get(f'CTYPE{i}', '')
|
|
64
|
+
if 'FREQ' in ctype.upper():
|
|
65
|
+
crval = hdr.get(f'CRVAL{i}', 0.0)
|
|
66
|
+
band_ref = crval * 1e-9 # Convert from Hz to GHz
|
|
67
|
+
break
|
|
68
|
+
|
|
69
|
+
|
|
70
|
+
return beam_arcsec, beam_area_arcsec2, beam_area_sr
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import os
|
|
2
|
+
|
|
3
|
+
import matplotlib.pyplot as plt
|
|
4
|
+
import numpy as np
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
def plot_fit_summary(cutout, cutout_masked_full, model, residual, output_dir, label_name="fit", dpi=300,
|
|
8
|
+
box_size=None, poly_order=None, nmse=None):
|
|
9
|
+
"""
|
|
10
|
+
Save 2D and 3D plots of cutout, model, and residual.
|
|
11
|
+
|
|
12
|
+
Parameters
|
|
13
|
+
----------
|
|
14
|
+
cutout : 2D array
|
|
15
|
+
Original data image (cutout).
|
|
16
|
+
model : 2D array
|
|
17
|
+
Fitted Gaussian + background model.
|
|
18
|
+
residual : 2D array
|
|
19
|
+
Difference between data and model.
|
|
20
|
+
output_dir : str
|
|
21
|
+
Directory to save PNG files.
|
|
22
|
+
label : str
|
|
23
|
+
Filename prefix for saving.
|
|
24
|
+
dpi : int
|
|
25
|
+
Resolution for PNG files.
|
|
26
|
+
"""
|
|
27
|
+
os.makedirs(output_dir, exist_ok=True)
|
|
28
|
+
|
|
29
|
+
# Shared color scale based on cutout range, ignoring NaNs
|
|
30
|
+
vmin_shared = np.nanmin(cutout)
|
|
31
|
+
vmax_shared = np.nanmax(cutout)
|
|
32
|
+
|
|
33
|
+
# Individual residual rescaled range, ignoring NaNs
|
|
34
|
+
vmin_resid = np.nanmin(residual)
|
|
35
|
+
vmax_resid = np.nanmax(residual)
|
|
36
|
+
|
|
37
|
+
data_list = [cutout, cutout_masked_full, model, residual, residual]
|
|
38
|
+
file_tags = ["cutout", "cutout masked full", "model", "residual", "residual_rescaled"]
|
|
39
|
+
|
|
40
|
+
if box_size is not None:
|
|
41
|
+
titles = [
|
|
42
|
+
f"Map Cutout Back. subt. • Box = {box_size} px ",
|
|
43
|
+
f"Original Map Cutout masked • Box = {box_size} px ",
|
|
44
|
+
f"Model (Gauss. + Backgr.) • Box = {box_size} px ",
|
|
45
|
+
f"Residual (Data − Model) • Box = {box_size} px ",
|
|
46
|
+
f"Residual (Rescaled) • Box = {box_size} px "
|
|
47
|
+
]
|
|
48
|
+
else:
|
|
49
|
+
titles = [
|
|
50
|
+
"Map Cutout Back. subt • Box = NaN ",
|
|
51
|
+
"Original Map Cutout masked • Box = NaN ",
|
|
52
|
+
"Model (Gauss. + Backgr.) • Box = NaN ",
|
|
53
|
+
"Residual (Data − Model) • Box = NaN ",
|
|
54
|
+
"Residual (Rescaled) • Box = NaN "
|
|
55
|
+
]
|
|
56
|
+
|
|
57
|
+
# Color limits: first three plots share cutout scale, last uses residual scale
|
|
58
|
+
vmins = [vmin_shared, vmin_shared, vmin_shared, vmin_resid]
|
|
59
|
+
vmaxs = [vmax_shared, vmax_shared, vmax_shared, vmax_resid]
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
# ------------------------ 2D plots --------------------------- #
|
|
63
|
+
for arr, title, tag, vmin, vmax in zip(data_list, titles, file_tags, vmins, vmaxs):
|
|
64
|
+
fig, ax = plt.subplots(figsize=(5, 4))
|
|
65
|
+
|
|
66
|
+
im = ax.imshow(arr, origin='lower', cmap='viridis', aspect='auto', vmin=vmin, vmax=vmax)
|
|
67
|
+
cb = fig.colorbar(im, ax=ax)
|
|
68
|
+
cb.set_label("Flux (Jy)", fontweight="bold", fontsize=9)
|
|
69
|
+
|
|
70
|
+
# Bold colorbar tick labels
|
|
71
|
+
cb.ax.tick_params(labelsize=8)
|
|
72
|
+
for tick in cb.ax.get_yticklabels():
|
|
73
|
+
tick.set_fontweight("bold")
|
|
74
|
+
|
|
75
|
+
# Bold axis labels
|
|
76
|
+
ax.set_xlabel("X (pix)", fontweight="bold", fontsize=9)
|
|
77
|
+
ax.set_ylabel("Y (pix)", fontweight="bold", fontsize=9)
|
|
78
|
+
|
|
79
|
+
# Bold tick labels
|
|
80
|
+
ax.tick_params(axis='both', labelsize=8)
|
|
81
|
+
for tick in ax.get_xticklabels():
|
|
82
|
+
tick.set_fontweight("bold")
|
|
83
|
+
for tick in ax.get_yticklabels():
|
|
84
|
+
tick.set_fontweight("bold")
|
|
85
|
+
|
|
86
|
+
# Format title with fit info, if provided
|
|
87
|
+
if box_size is not None and poly_order is not None and nmse is not None:
|
|
88
|
+
subtitle = f"• Polynomial Order = {poly_order} • NMSE = {nmse:.3f}"
|
|
89
|
+
full_title = f"{title}\n{subtitle}"
|
|
90
|
+
else:
|
|
91
|
+
full_title = title
|
|
92
|
+
|
|
93
|
+
ax.set_title(full_title, fontsize=10, fontweight="bold", linespacing=1.4)
|
|
94
|
+
|
|
95
|
+
# Save with tight bounding box
|
|
96
|
+
outname = os.path.join(output_dir, f"{label_name}_{tag}_2D.png")
|
|
97
|
+
fig.savefig(outname, dpi=dpi, bbox_inches='tight')
|
|
98
|
+
plt.close(fig)
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
# ------------------------ 3D surface plots --------------------------- #
|
|
102
|
+
ny, nx = cutout.shape
|
|
103
|
+
x = np.arange(nx)
|
|
104
|
+
y = np.arange(ny)
|
|
105
|
+
X, Y = np.meshgrid(x, y)
|
|
106
|
+
|
|
107
|
+
for arr, title, tag, vmin, vmax in zip(data_list, titles, file_tags, vmins, vmaxs):
|
|
108
|
+
fig = plt.figure(figsize=(6, 5))
|
|
109
|
+
ax = fig.add_subplot(111, projection='3d')
|
|
110
|
+
|
|
111
|
+
# Plot 3D surface
|
|
112
|
+
surf = ax.plot_surface(X, Y, arr, cmap='viridis',
|
|
113
|
+
vmin=vmin, vmax=vmax,
|
|
114
|
+
linewidth=0, antialiased=True)
|
|
115
|
+
|
|
116
|
+
# Z limits scaled to color range
|
|
117
|
+
ax.set_zlim(vmin, vmax)
|
|
118
|
+
|
|
119
|
+
# Set title with fit info
|
|
120
|
+
if poly_order is not None and nmse is not None:
|
|
121
|
+
subtitle = f"• Polynomial Order = {poly_order} • NMSE = {nmse:.3f}"
|
|
122
|
+
full_title = f"{title}\n{subtitle}"
|
|
123
|
+
else:
|
|
124
|
+
full_title = title
|
|
125
|
+
|
|
126
|
+
ax.set_title(full_title, fontsize=9, fontweight="bold", pad=-40)
|
|
127
|
+
|
|
128
|
+
# Axis labels
|
|
129
|
+
ax.set_xlabel("X (pix)", fontweight="bold", fontsize=9, labelpad=2)
|
|
130
|
+
ax.set_ylabel("Y (pix)", fontweight="bold", fontsize=9, labelpad=2)
|
|
131
|
+
ax.set_zlabel("Flux (Jy)", fontweight="bold", fontsize=9, labelpad=2)
|
|
132
|
+
|
|
133
|
+
# Bold tick labels
|
|
134
|
+
for label in ax.get_xticklabels():
|
|
135
|
+
label.set_fontweight("bold")
|
|
136
|
+
label.set_fontsize(8)
|
|
137
|
+
for label in ax.get_yticklabels():
|
|
138
|
+
label.set_fontweight("bold")
|
|
139
|
+
label.set_fontsize(8)
|
|
140
|
+
for label in ax.get_zticklabels():
|
|
141
|
+
label.set_fontweight("bold")
|
|
142
|
+
label.set_fontsize(8)
|
|
143
|
+
|
|
144
|
+
# Adjust margins to make room
|
|
145
|
+
fig.subplots_adjust(left=0.05, right=0.95, top=0.85, bottom=0.05)
|
|
146
|
+
|
|
147
|
+
# Save using tight layout with padding
|
|
148
|
+
outname = os.path.join(output_dir, f"{label_name}_{tag}_3D.png")
|
|
149
|
+
fig.savefig(outname, dpi=dpi, bbox_inches='tight', pad_inches=0.25)
|
|
150
|
+
plt.close(fig)
|