visualens 0.1.0__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.
- visualens-0.1.0/PKG-INFO +24 -0
- visualens-0.1.0/README.md +3 -0
- visualens-0.1.0/pyproject.toml +30 -0
- visualens-0.1.0/setup.cfg +4 -0
- visualens-0.1.0/visualens/__init__.py +5 -0
- visualens-0.1.0/visualens/catalog.py +512 -0
- visualens-0.1.0/visualens/fits_image.py +604 -0
- visualens-0.1.0/visualens/gui.py +313 -0
- visualens-0.1.0/visualens/lenstool_model/im2source.py +145 -0
- visualens-0.1.0/visualens/lenstool_model/lenstool_model.py +800 -0
- visualens-0.1.0/visualens/lenstool_model/simulate_image/simulate_image.py +256 -0
- visualens-0.1.0/visualens/lenstool_model/simulate_image/utils.py +301 -0
- visualens-0.1.0/visualens/lenstool_model/utils/file_makers.py +204 -0
- visualens-0.1.0/visualens/lenstool_model/utils/lenstool_to_lenstronomy.py +102 -0
- visualens-0.1.0/visualens/lenstool_model/utils/operations.py +32 -0
- visualens-0.1.0/visualens/lenstool_model/utils/param_extractors.py +622 -0
- visualens-0.1.0/visualens/lenstool_model/utils/redshift_extractors.py +81 -0
- visualens-0.1.0/visualens/lenstool_model/utils/utils_general.py +617 -0
- visualens-0.1.0/visualens/main.py +19 -0
- visualens-0.1.0/visualens/sliders_TEST.py +213 -0
- visualens-0.1.0/visualens/source_extraction/source_extract.py +23 -0
- visualens-0.1.0/visualens/utils/__init__.py +0 -0
- visualens-0.1.0/visualens/utils/utils_Qt/drag_widgets.py +339 -0
- visualens-0.1.0/visualens/utils/utils_Qt/selectable_classes.py +268 -0
- visualens-0.1.0/visualens/utils/utils_Qt/utils_general.py +104 -0
- visualens-0.1.0/visualens/utils/utils_astro/cat_manip.py +293 -0
- visualens-0.1.0/visualens/utils/utils_astro/get_cosmology.py +13 -0
- visualens-0.1.0/visualens/utils/utils_astro/utils_general.py +205 -0
- visualens-0.1.0/visualens/utils/utils_astro/utils_lensing.py +16 -0
- visualens-0.1.0/visualens/utils/utils_fits/utils_fits_image.py +111 -0
- visualens-0.1.0/visualens/utils/utils_general/sort_points.py +293 -0
- visualens-0.1.0/visualens/utils/utils_general/utils_general.py +222 -0
- visualens-0.1.0/visualens/utils/utils_plots/plot_utils_general.py +230 -0
- visualens-0.1.0/visualens/utils/utils_plots/plot_utils_specific.py +48 -0
- visualens-0.1.0/visualens/utils/utils_plots/plt_framework.py +78 -0
- visualens-0.1.0/visualens.egg-info/PKG-INFO +24 -0
- visualens-0.1.0/visualens.egg-info/SOURCES.txt +38 -0
- visualens-0.1.0/visualens.egg-info/dependency_links.txt +1 -0
- visualens-0.1.0/visualens.egg-info/requires.txt +11 -0
- visualens-0.1.0/visualens.egg-info/top_level.txt +1 -0
visualens-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: visualens
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: A visual interface to explore and manipulate astronomical images, catalogs, and lens models created using Lenstool.
|
|
5
|
+
Author-email: Tom Resseguier <tresseg1@jhu.edu>
|
|
6
|
+
Project-URL: Homepage, https://github.com/tomresseguier/Lenstool_GUI/
|
|
7
|
+
Project-URL: Issues, https://github.com/tomresseguier/Lenstool_GUI/issues
|
|
8
|
+
Requires-Python: >=3.8
|
|
9
|
+
Description-Content-Type: text/markdown
|
|
10
|
+
Requires-Dist: numpy>=1.21
|
|
11
|
+
Requires-Dist: astropy>=5.0
|
|
12
|
+
Requires-Dist: matplotlib>=3.5
|
|
13
|
+
Requires-Dist: PyQt5>=5.15
|
|
14
|
+
Requires-Dist: pyqtgraph>=0.12
|
|
15
|
+
Requires-Dist: scipy>=1.7
|
|
16
|
+
Requires-Dist: pandas>=1.4
|
|
17
|
+
Requires-Dist: tqdm>=4.60
|
|
18
|
+
Requires-Dist: corner>=2.2
|
|
19
|
+
Requires-Dist: stsynphot>=0.3
|
|
20
|
+
Requires-Dist: lenstronomy>=1.1
|
|
21
|
+
|
|
22
|
+
# Lenstool GUI
|
|
23
|
+
|
|
24
|
+
A visual interface to explore astronomical images, catalogs, and lens models created using Lenstool.
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "visualens"
|
|
3
|
+
version = "0.1.0"
|
|
4
|
+
authors = [
|
|
5
|
+
{ name="Tom Resseguier", email="tresseg1@jhu.edu" },
|
|
6
|
+
]
|
|
7
|
+
description = "A visual interface to explore and manipulate astronomical images, catalogs, and lens models created using Lenstool."
|
|
8
|
+
readme = "README.md"
|
|
9
|
+
requires-python = ">=3.8"
|
|
10
|
+
dependencies = [
|
|
11
|
+
"numpy>=1.21",
|
|
12
|
+
"astropy>=5.0",
|
|
13
|
+
"matplotlib>=3.5",
|
|
14
|
+
"PyQt5>=5.15",
|
|
15
|
+
"pyqtgraph>=0.12",
|
|
16
|
+
"scipy>=1.7",
|
|
17
|
+
"pandas>=1.4",
|
|
18
|
+
"tqdm>=4.60",
|
|
19
|
+
"corner>=2.2",
|
|
20
|
+
"stsynphot>=0.3",
|
|
21
|
+
"lenstronomy>=1.1"
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
[project.urls]
|
|
25
|
+
"Homepage" = "https://github.com/tomresseguier/Lenstool_GUI/"
|
|
26
|
+
"Issues" = "https://github.com/tomresseguier/Lenstool_GUI/issues"
|
|
27
|
+
|
|
28
|
+
[build-system]
|
|
29
|
+
requires = ["setuptools>=61.0"]
|
|
30
|
+
build-backend = "setuptools.build_meta"
|
|
@@ -0,0 +1,512 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import numpy as np
|
|
3
|
+
from matplotlib import pyplot as plt
|
|
4
|
+
from matplotlib.patches import Ellipse, Rectangle
|
|
5
|
+
from tqdm import tqdm
|
|
6
|
+
|
|
7
|
+
import PyQt5
|
|
8
|
+
import pyqtgraph as pg
|
|
9
|
+
from astropy.table import Table
|
|
10
|
+
import pandas as pd
|
|
11
|
+
|
|
12
|
+
###############################################################################
|
|
13
|
+
from .source_extraction.match_cat import run_match
|
|
14
|
+
from .utils.utils_astro.cat_manip import match_cat2
|
|
15
|
+
from .utils.utils_plots.plot_utils_general import *
|
|
16
|
+
from .utils.utils_Qt.selectable_classes import SelectableEllipse, SelectableScatter, SelectSources, ellipse_maker_ROI
|
|
17
|
+
from .utils.utils_Qt.utils_general import make_handles, InRectangle, make_full_color
|
|
18
|
+
from .utils.utils_general.utils_general import make_colnames_dict
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def open_cat(cat_path) :
|
|
26
|
+
if '.fits' in cat_path :
|
|
27
|
+
cat = Table.read(cat_path, format='fits')
|
|
28
|
+
else :
|
|
29
|
+
with open(cat_path, 'r') as raw_cat :
|
|
30
|
+
first_line, second_line = raw_cat.readlines()[0:2]
|
|
31
|
+
print(first_line)
|
|
32
|
+
start_line = 1 if second_line.startswith('--') else 0
|
|
33
|
+
if len(first_line.split()) > len(first_line.split(',')) :
|
|
34
|
+
cat_df = pd.read_csv(cat_path, sep='\s+', skip_blank_lines=True, comment='#')[start_line:].apply(pd.to_numeric, errors='coerce')
|
|
35
|
+
else :
|
|
36
|
+
cat_df = pd.read_csv(cat_path, skip_blank_lines=True, comment='#')[start_line:].apply(pd.to_numeric, errors='coerce')
|
|
37
|
+
cat = Table.from_pandas(cat_df)
|
|
38
|
+
return cat
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def make_uniform_names_cat(cat, self) :
|
|
42
|
+
uniform_names_cat = cat.copy()
|
|
43
|
+
colnames_dict = make_colnames_dict(cat, use_default_names=self.use_default_names)
|
|
44
|
+
|
|
45
|
+
print('Column names to be used:')
|
|
46
|
+
print(colnames_dict)
|
|
47
|
+
|
|
48
|
+
for colname in colnames_dict.keys() :
|
|
49
|
+
if colnames_dict[colname] is not None :
|
|
50
|
+
|
|
51
|
+
if colname in uniform_names_cat.colnames :
|
|
52
|
+
uniform_names_cat[colname] = uniform_names_cat[colnames_dict[colname]]
|
|
53
|
+
elif colname in ['ra', 'dec'] :
|
|
54
|
+
uniform_names_cat.rename_column(colnames_dict[colname], colname)
|
|
55
|
+
else :
|
|
56
|
+
uniform_names_cat.add_column( uniform_names_cat[colnames_dict[colname]], name=colname )
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if colnames_dict['a'] != 'A_IMAGE' and colnames_dict['b'] != 'B_IMAGE' :
|
|
60
|
+
if self.units is None :
|
|
61
|
+
self.units = input("ellipticity parameters " + str(colnames_dict['a']) \
|
|
62
|
+
+ " and " + str(colnames_dict['b']) + " in pixels? [y][arcsec][deg]")
|
|
63
|
+
if self.units == 'deg' :
|
|
64
|
+
uniform_names_cat.replace_column( 'a', uniform_names_cat['a']/(self.fits_image.pix_deg_scale) )
|
|
65
|
+
uniform_names_cat.replace_column( 'b', uniform_names_cat['b']/(self.fits_image.pix_deg_scale) )
|
|
66
|
+
if self.units == 'arcsec' :
|
|
67
|
+
uniform_names_cat.replace_column( 'a', uniform_names_cat['a']/(self.fits_image.pix_deg_scale*3600) )
|
|
68
|
+
uniform_names_cat.replace_column( 'b', uniform_names_cat['b']/(self.fits_image.pix_deg_scale*3600) )
|
|
69
|
+
|
|
70
|
+
#if colnames_dict['x']==None :
|
|
71
|
+
x, y = self.fits_image.world_to_image(uniform_names_cat['ra'], uniform_names_cat['dec'], unit='deg')
|
|
72
|
+
uniform_names_cat['x'] = x
|
|
73
|
+
uniform_names_cat['y'] = y
|
|
74
|
+
|
|
75
|
+
yesno = 'y'
|
|
76
|
+
if colnames_dict['a'] is not None and not self.use_default_names :
|
|
77
|
+
yesno = input("'a', 'b' and 'theta' columns found in catalog. Use them as ellipticity parameters (if not, sources will be shown as circles)? [y] or [n]")
|
|
78
|
+
if colnames_dict['a'] is None or yesno != 'y' :
|
|
79
|
+
size = np.min([self.fits_image.image_data.shape[0], self.fits_image.image_data.shape[1]]) / 1000
|
|
80
|
+
uniform_names_cat['a'] = np.full(len(uniform_names_cat), size)
|
|
81
|
+
uniform_names_cat['b'] = np.full(len(uniform_names_cat), size)
|
|
82
|
+
uniform_names_cat['theta'] = np.full(len(uniform_names_cat), 0.)
|
|
83
|
+
return uniform_names_cat
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def initialize_catalog(cat, self) :
|
|
87
|
+
##### Combine or just open the catalog #####
|
|
88
|
+
ref_path = None
|
|
89
|
+
if isinstance(cat, str) :
|
|
90
|
+
ref_path = cat
|
|
91
|
+
cat = open_cat(cat)
|
|
92
|
+
elif isinstance(cat, list) :
|
|
93
|
+
ref_path = os.path.dirname(cat[0])
|
|
94
|
+
run_match(cat[0], cat[1])
|
|
95
|
+
for i in range(len(cat)-3) :
|
|
96
|
+
run_match('matched_A_B.fits', cat[i+2])
|
|
97
|
+
matched_cat = run_match('matched_A_B.fits', cat[-1])
|
|
98
|
+
cat = Table(matched_cat[1].data)
|
|
99
|
+
os.remove('matched_A_B.fits')
|
|
100
|
+
|
|
101
|
+
##### Standardize the column names #####
|
|
102
|
+
uniform_names_cat = make_uniform_names_cat(cat, self)
|
|
103
|
+
return uniform_names_cat, ref_path
|
|
104
|
+
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class catalog :
|
|
113
|
+
def __init__(self, cat, fits_image, color=[0., 1., 1., 0., 0.5], mag_colnames=['magAB_F814W', 'magAB_F435W'], use_default_names=True, units=None) :
|
|
114
|
+
self.fits_image = fits_image
|
|
115
|
+
|
|
116
|
+
self.mag_colnames = mag_colnames
|
|
117
|
+
self.use_default_names = use_default_names
|
|
118
|
+
self.units = units
|
|
119
|
+
|
|
120
|
+
self.cat, self.ref_path = initialize_catalog(cat, self)
|
|
121
|
+
self.qtItems = [] #np.empty(len(self.cat), dtype=PyQt5.QtWidgets.QGraphicsEllipseItem)
|
|
122
|
+
#self.qtItems = [PyQt5.QtWidgets.QGraphicsEllipseItem() for _ in range(len(self.cat))]
|
|
123
|
+
self.qtItems_column = [] #np.empty(len(self.cat), dtype=pg.TextItem)
|
|
124
|
+
#self.qtItems = np.empty(len(self.cat), dtype=utils.utils_classes.selectable_ellipse.SelectableEllipse)
|
|
125
|
+
self.color = color if color is not None else [0., 1., 1., 0., 0.5]
|
|
126
|
+
self.selection_mask = np.full(len(self.cat), False)
|
|
127
|
+
self.selection_regions = []
|
|
128
|
+
self.Scatter_widget = None
|
|
129
|
+
self.x_axis_cleaned = np.full(len(self.cat), None)
|
|
130
|
+
self.y_axis_cleaned = np.full(len(self.cat), None)
|
|
131
|
+
|
|
132
|
+
#self.is_plotted = False
|
|
133
|
+
#self.is_plotted_column = False
|
|
134
|
+
|
|
135
|
+
|
|
136
|
+
def make_mask_naninf(self, xy_axes=None) :
|
|
137
|
+
#mag_F444W = flux_muJy_to_magAB(self.cat['f444w_tot_0'])
|
|
138
|
+
#mag_F090W = flux_muJy_to_magAB(self.cat['f090w_tot_0'])
|
|
139
|
+
#x_axis = mag_F444W
|
|
140
|
+
#y_axis = mag_F090W - mag_F444W
|
|
141
|
+
|
|
142
|
+
if xy_axes is None :
|
|
143
|
+
mag_F814W = self.cat[self.mag_colnames[0]]
|
|
144
|
+
mag_F435W = self.cat[self.mag_colnames[1]]
|
|
145
|
+
x_axis = mag_F814W
|
|
146
|
+
y_axis = mag_F435W - mag_F814W
|
|
147
|
+
else :
|
|
148
|
+
x_axis = self.cat[xy_axes[0]]
|
|
149
|
+
y_axis = self.cat[xy_axes[1]]
|
|
150
|
+
|
|
151
|
+
nan_mask = np.logical_not(np.isnan(x_axis)) & np.logical_not(np.isnan(y_axis))
|
|
152
|
+
inf_mask = (x_axis!=np.inf) & (y_axis!=np.inf)
|
|
153
|
+
#extremes_mask = (x_axis>0) & (x_axis<50)
|
|
154
|
+
self.mask_naninf = nan_mask & inf_mask
|
|
155
|
+
self.x_axis_cleaned = x_axis[self.mask_naninf]
|
|
156
|
+
self.y_axis_cleaned = y_axis[self.mask_naninf]
|
|
157
|
+
|
|
158
|
+
self.cat = self.cat[self.mask_naninf]
|
|
159
|
+
self.plot()
|
|
160
|
+
#self.qtItems = np.empty(len(self.cat), dtype=PyQt5.QtWidgets.QGraphicsEllipseItem)
|
|
161
|
+
self.selection_mask = np.full(len(self.cat), False)
|
|
162
|
+
|
|
163
|
+
def plot(self, scale=1., color=None, text_column=None, linewidth=3, marker=None) :
|
|
164
|
+
self.clear()
|
|
165
|
+
print('Plotting...')
|
|
166
|
+
x = self.cat['x']
|
|
167
|
+
y = self.cat['y']
|
|
168
|
+
semi_major = self.cat['a'] * scale
|
|
169
|
+
semi_minor = self.cat['b'] * scale
|
|
170
|
+
angle = self.cat['theta']
|
|
171
|
+
for i in tqdm(range(len(semi_major))) :
|
|
172
|
+
ellipse = self.plot_one_object(x[i], y[i], semi_major[i], semi_minor[i], angle[i], i, \
|
|
173
|
+
color=color, linewidth=linewidth, marker=marker, size=scale*15.)
|
|
174
|
+
self.qtItems.append(ellipse)
|
|
175
|
+
|
|
176
|
+
if text_column is not None:
|
|
177
|
+
self.plot_column(text_column, color=color)
|
|
178
|
+
|
|
179
|
+
def plot_column(self, text_column, color=None, n_digit=3):
|
|
180
|
+
self.clear_column()
|
|
181
|
+
print('Plotting...')
|
|
182
|
+
if text_column not in self.cat.colnames:
|
|
183
|
+
print(f"Column '{text_column}' not found in catalog")
|
|
184
|
+
return
|
|
185
|
+
|
|
186
|
+
color = self.color if color is None else color
|
|
187
|
+
color = list(np.array(self.color[:3])*255)
|
|
188
|
+
|
|
189
|
+
for i in tqdm(range(len(self.cat))) :
|
|
190
|
+
to_plot = self.cat[text_column][i]
|
|
191
|
+
text = to_plot if type(to_plot)==str else f"{to_plot:.{n_digit}g}"
|
|
192
|
+
text_item = pg.TextItem(text, color=color)
|
|
193
|
+
|
|
194
|
+
# Get ellipse position and size for offset calculation
|
|
195
|
+
x = self.cat['x'][i]
|
|
196
|
+
y = self.fits_image.image_data.shape[0] - self.cat['y'][i] # Flip y to match PyQtGraph convention
|
|
197
|
+
semi_major = self.cat['a'][i]
|
|
198
|
+
semi_minor = self.cat['b'][i]
|
|
199
|
+
|
|
200
|
+
# Position text slightly offset from the ellipse
|
|
201
|
+
offset = max(semi_major, semi_minor)
|
|
202
|
+
text_item.setPos(x + offset/2, y - offset/2)
|
|
203
|
+
|
|
204
|
+
font = PyQt5.QtGui.QFont()
|
|
205
|
+
font.setPointSize(15)
|
|
206
|
+
text_item.setFont(font)
|
|
207
|
+
|
|
208
|
+
self.fits_image.qt_image.addItem(text_item)
|
|
209
|
+
self.qtItems_column.append(text_item)
|
|
210
|
+
|
|
211
|
+
def clear(self) :
|
|
212
|
+
#qtItems_list = self.fits_image.qt_image.getView().allChildItems()
|
|
213
|
+
print('Clearing galaxies...')
|
|
214
|
+
for qtItem in self.qtItems :
|
|
215
|
+
self.fits_image.qt_image.removeItem(qtItem)
|
|
216
|
+
del qtItem
|
|
217
|
+
self.qtItems.clear()
|
|
218
|
+
|
|
219
|
+
self.clear_column()
|
|
220
|
+
|
|
221
|
+
def clear_column(self) :
|
|
222
|
+
print('Clearing column labels...')
|
|
223
|
+
for text_item in self.qtItems_column:
|
|
224
|
+
self.fits_image.qt_image.removeItem(text_item)
|
|
225
|
+
del text_item
|
|
226
|
+
self.qtItems_column.clear()
|
|
227
|
+
|
|
228
|
+
def clear_selection(self) :
|
|
229
|
+
self.selection_mask[np.full(len(self.cat), True)] = False
|
|
230
|
+
self.selection_regions.clear()
|
|
231
|
+
self.clear()
|
|
232
|
+
self.plot()
|
|
233
|
+
|
|
234
|
+
def plot_one_object(self, x, y, semi_major, semi_minor, angle, idx, color=None, linewidth=3, marker=None, size=15) :
|
|
235
|
+
if color is None :
|
|
236
|
+
color = self.color
|
|
237
|
+
#make the flip to accomosate pyqtgraph's plotting conventions
|
|
238
|
+
y = self.fits_image.image_data.shape[0] - y
|
|
239
|
+
angle = -angle
|
|
240
|
+
#####################################################################
|
|
241
|
+
if marker==None or marker=='ellipse' :
|
|
242
|
+
if self.Scatter_widget==None :
|
|
243
|
+
scatter_pos = None
|
|
244
|
+
else :
|
|
245
|
+
scatter_pos = (self.x_axis_cleaned[idx], self.y_axis_cleaned[idx])
|
|
246
|
+
to_plot = SelectableEllipse(x-semi_major/2, y-semi_minor/2, semi_major, semi_minor, idx, self.selection_mask,
|
|
247
|
+
color, linewidth=linewidth, scatter_pos=scatter_pos,
|
|
248
|
+
Scatter_widget=self.Scatter_widget)
|
|
249
|
+
to_plot.setTransformOriginPoint( PyQt5.QtCore.QPointF(x, y) )
|
|
250
|
+
to_plot.setRotation(angle)
|
|
251
|
+
else :
|
|
252
|
+
to_plot = pg.ScatterPlotItem(size=size, symbol=marker)
|
|
253
|
+
color = make_full_color(color)
|
|
254
|
+
if color[3]==0 : #filled_markers==False
|
|
255
|
+
to_plot.setPen( pg.mkPen(color[:3], width=2) ) #no outline
|
|
256
|
+
to_plot.setBrush( pg.mkBrush([0,0,0,0]) )
|
|
257
|
+
else :
|
|
258
|
+
to_plot.setPen( pg.mkPen([0,0,0,0], width=0.1) ) #outline makes cross thicker
|
|
259
|
+
to_plot.setBrush( pg.mkBrush(color[:-2]) )
|
|
260
|
+
|
|
261
|
+
to_plot.setData([x], [y])
|
|
262
|
+
|
|
263
|
+
self.fits_image.qt_image.addItem(to_plot)
|
|
264
|
+
return to_plot
|
|
265
|
+
|
|
266
|
+
def make_selection_panel(self, xy_axes=None) :
|
|
267
|
+
self.make_mask_naninf(xy_axes=xy_axes)
|
|
268
|
+
|
|
269
|
+
self.Scatter_widget = SelectableScatter(self)
|
|
270
|
+
#self.Scatter_widget.setTitle('Red sequence')
|
|
271
|
+
|
|
272
|
+
self.Scatter_widget.setAspectLocked(lock=True, ratio=1)
|
|
273
|
+
self.Scatter_widget.autoRange()
|
|
274
|
+
#self.Scatter_widget.setSizePolicy(pg.QtWidgets.QSizePolicy.Fixed, pg.QtWidgets.QSizePolicy.Expanding)
|
|
275
|
+
self.fits_image.qt_layout.addWidget(self.Scatter_widget)
|
|
276
|
+
|
|
277
|
+
if xy_axes is not None :
|
|
278
|
+
self.Scatter_widget.setLabel('bottom', xy_axes[0])
|
|
279
|
+
self.Scatter_widget.setLabel('left', xy_axes[1])
|
|
280
|
+
|
|
281
|
+
#self.selection_mask = np.full(len(self.mag_F444W_cleaned), False)
|
|
282
|
+
self.plot()
|
|
283
|
+
|
|
284
|
+
def remove_selection_panel(self) :
|
|
285
|
+
self.Scatter_widget.hide()
|
|
286
|
+
del self.Scatter_widget
|
|
287
|
+
self.Scatter_widget = None
|
|
288
|
+
|
|
289
|
+
def make_image_ROI(self) :
|
|
290
|
+
"""
|
|
291
|
+
Creates an ellipse ROI to add elliptic sources to the catalog by drawing them by hand.
|
|
292
|
+
"""
|
|
293
|
+
center_y = self.fits_image.image_data.shape[0]/2
|
|
294
|
+
center_x = self.fits_image.image_data.shape[1]/2
|
|
295
|
+
self.image_ROI = ellipse_maker_ROI([center_x-200, center_y-100], [400, 200], self.fits_image.qt_image, self.fits_image.window, self.cat)
|
|
296
|
+
make_handles(self.image_ROI)
|
|
297
|
+
self.fits_image.qt_image.addItem(self.image_ROI)
|
|
298
|
+
|
|
299
|
+
def make_selection_ROI(self) :
|
|
300
|
+
"""
|
|
301
|
+
Creates a rectangle ROI to select all sources inside of it.
|
|
302
|
+
"""
|
|
303
|
+
self.fits_image.image_widget.cat = self
|
|
304
|
+
self.select_sources = SelectSources(self)
|
|
305
|
+
|
|
306
|
+
def save_selection_mask(self, path=None) :
|
|
307
|
+
self.selection_mask_path = self.make_path(path, self.ref_path, 'selection_mask.npy')
|
|
308
|
+
np.save(self.selection_mask_path, self.selection_mask)
|
|
309
|
+
print("Selection mask saved at " + self.selection_mask_path)
|
|
310
|
+
|
|
311
|
+
def load_selection_mask(self, path=None) :
|
|
312
|
+
self.selection_mask_path = self.make_path(path, self.ref_path, 'selection_mask.npy')
|
|
313
|
+
self.selection_mask = np.load(self.selection_mask_path)
|
|
314
|
+
|
|
315
|
+
def save_selection_regions(self, path=None) :
|
|
316
|
+
self.selection_regions_path = self.make_path(path, self.fits_image.image_path, 'selection_regions.npy')
|
|
317
|
+
np.save(self.selection_regions_path, self.selection_regions)
|
|
318
|
+
|
|
319
|
+
def load_selection_regions(self, path=None, name='selection_regions.npy') :
|
|
320
|
+
self.selection_regions_path = self.make_path(path, self.fits_image.image_path, name)
|
|
321
|
+
self.selection_regions = np.load(self.selection_regions_path).tolist()
|
|
322
|
+
|
|
323
|
+
size_y = self.fits_image.qt_image.image.shape[0]
|
|
324
|
+
for rect_params in self.selection_regions :
|
|
325
|
+
indiv_mask = InRectangle(self.cat['x'], size_y - self.cat['y'], rect_params)
|
|
326
|
+
self.selection_mask[indiv_mask] = True
|
|
327
|
+
|
|
328
|
+
fig, ax = plt.subplots()
|
|
329
|
+
ax.axis('equal')
|
|
330
|
+
size = max(self.fits_image.qt_image.image.shape[0], self.fits_image.qt_image.image.shape[1])
|
|
331
|
+
#ax.invert_yaxis()
|
|
332
|
+
ax.set_ylim([size+2000, -2000])
|
|
333
|
+
ax.set_xlim([-4000, size+4000])
|
|
334
|
+
|
|
335
|
+
for i, rect_params in enumerate(self.selection_regions) :
|
|
336
|
+
x0, y0, a, b, angle = rect_params
|
|
337
|
+
#x1, y1 = x0, y0
|
|
338
|
+
#x2, y2 = x0 + a*np.cos(angle), y0 + a*np.sin(angle)
|
|
339
|
+
#x3, y3 = x0 + a*np.cos(angle) - b*np.sin(angle), y0 + a*np.sin(angle) + b*np.cos(angle)
|
|
340
|
+
#x4, y4 = x0 - b*np.sin(angle), y0 + b*np.cos(angle)
|
|
341
|
+
#ax.plot([x1, x2, x3, x4, x1], size_y-np.array([y1, y2, y3, y4, y1]), c='b')
|
|
342
|
+
#ax.axis('equal')
|
|
343
|
+
ax.add_patch( Rectangle((x0, y0), a, b, angle=angle*180/np.pi, alpha=0.4 ))
|
|
344
|
+
ax.text(x0, y0, str(i))
|
|
345
|
+
fig.show()
|
|
346
|
+
plt.pause(0.05)
|
|
347
|
+
|
|
348
|
+
def make_path(self, path, ref_path, name) :
|
|
349
|
+
if path is None :#and self.ref_path is not None :
|
|
350
|
+
#to_return = os.path.join(os.path.dirname(ref_path), name)
|
|
351
|
+
to_return = os.path.join(os.path.dirname(ref_path), os.path.basename(ref_path).split('.')[0] + '_' + name)
|
|
352
|
+
elif os.path.isdir(path) :
|
|
353
|
+
to_return = os.path.join(path, name)
|
|
354
|
+
elif os.path.isdir(os.path.dirname(path)) :
|
|
355
|
+
to_return = path
|
|
356
|
+
return to_return
|
|
357
|
+
|
|
358
|
+
|
|
359
|
+
def plot_one_galaxy_mpl(self, x, y, a, b, theta, color=[1,1,1], text=None, ax=None, linewidth=1., text_color='white', text_alpha=0.5) :
|
|
360
|
+
edgecolor = list(color).copy()
|
|
361
|
+
edgecolor.append(1)
|
|
362
|
+
facecolor = edgecolor.copy()
|
|
363
|
+
facecolor[-1] = 0
|
|
364
|
+
ellipse = Ellipse( (x, y), a, b, angle=theta, facecolor=facecolor, edgecolor=edgecolor, lw=linewidth )
|
|
365
|
+
if ax is None :
|
|
366
|
+
self.fits_image.mpl_ax.add_artist(ellipse)
|
|
367
|
+
if text is not None :
|
|
368
|
+
#self.fits_image.mpl_ax.text(x-1.5*b*np.abs(np.sin(theta)), y-1.5*b*np.abs(np.cos(theta)), text, color=edgecolor[:3], \
|
|
369
|
+
# ha='right', va='top')
|
|
370
|
+
offset = 0.85
|
|
371
|
+
theta_modulo = theta%180 * np.pi/180
|
|
372
|
+
if theta_modulo<np.pi/2 :
|
|
373
|
+
x_text, y_text = x+offset*b*np.abs(np.sin(theta_modulo)), y-offset*b*np.abs(np.cos(theta_modulo))
|
|
374
|
+
horizontalalignment, verticalalignment = 'left', 'top'
|
|
375
|
+
else :
|
|
376
|
+
x_text, y_text = x-offset*b*np.abs(np.sin(theta_modulo)), y-offset*b*np.abs(np.cos(theta_modulo))
|
|
377
|
+
horizontalalignment, verticalalignment = 'right', 'top'
|
|
378
|
+
self.fits_image.mpl_ax.text( x_text, y_text, text, c=text_color, alpha=1, fontsize=15, \
|
|
379
|
+
ha=horizontalalignment, va=verticalalignment, \
|
|
380
|
+
bbox=dict(facecolor=edgecolor[:3], alpha=text_alpha, edgecolor='none') )
|
|
381
|
+
else :
|
|
382
|
+
ax.add_artist(ellipse)
|
|
383
|
+
if text is not None :
|
|
384
|
+
offset = 0.85
|
|
385
|
+
theta_modulo = theta%180 * np.pi/180
|
|
386
|
+
if theta_modulo<np.pi/2 :
|
|
387
|
+
x_text, y_text = x+offset*b*np.abs(np.sin(theta_modulo)), y-offset*b*np.abs(np.cos(theta_modulo))
|
|
388
|
+
horizontalalignment, verticalalignment = 'left', 'top'
|
|
389
|
+
else :
|
|
390
|
+
x_text, y_text = x-offset*b*np.abs(np.sin(theta_modulo)), y-offset*b*np.abs(np.cos(theta_modulo))
|
|
391
|
+
horizontalalignment, verticalalignment = 'right', 'top'
|
|
392
|
+
#ax.text(x_text, y_text, text, color=edgecolor[:3], \
|
|
393
|
+
# ha=horizontalalignment, va=verticalalignment)
|
|
394
|
+
ax.text( x_text, y_text, text, c=text_color, alpha=1, fontsize=15, \
|
|
395
|
+
ha=horizontalalignment, va=verticalalignment, \
|
|
396
|
+
bbox=dict(facecolor=edgecolor[:3], alpha=text_alpha, edgecolor='none') )
|
|
397
|
+
|
|
398
|
+
def export_to_mult_file(self, file_path=None) :
|
|
399
|
+
if file_path is None :
|
|
400
|
+
file_path = os.path.join(os.path.dirname(self.fits_image.image_path), 'mult.lenstool')
|
|
401
|
+
|
|
402
|
+
sub_cat = self.cat[self.selection_mask] if True in self.selection_mask else self.cat
|
|
403
|
+
|
|
404
|
+
header = "#REFERENCE 0\n## id RA Dec a b theta z mag\n"
|
|
405
|
+
|
|
406
|
+
if 'THETA_WORLD' in sub_cat.colnames :
|
|
407
|
+
theta_colname = 'THETA_WORLD'
|
|
408
|
+
elif 'theta' in sub_cat.colnames :
|
|
409
|
+
theta_colname = 'theta'
|
|
410
|
+
print("Using 'theta' column in exported catalog, make sure theta is relative to world coordinates and not image coordinates!!")
|
|
411
|
+
else :
|
|
412
|
+
theta_colname = None
|
|
413
|
+
|
|
414
|
+
with open(file_path, 'w') as f :
|
|
415
|
+
f.write(header)
|
|
416
|
+
for index, row in enumerate(sub_cat) :
|
|
417
|
+
if theta_colname is not None :
|
|
418
|
+
if 'zb' in sub_cat.colnames and 'f814w_mag' in sub_cat.colnames :
|
|
419
|
+
line = (f"{row['id']:<3} {row['ra']:10.6f} {row['dec']:10.6f} "
|
|
420
|
+
f"{row['a']:8.6f} {row['b']:8.6f} {row[theta_colname]:8.6f} "
|
|
421
|
+
f"{row['zb']:8.6f} {row['f814w_mag']:8.6f}\n")
|
|
422
|
+
else :
|
|
423
|
+
line = (f"{row['id']:<3} {row['ra']:10.6f} {row['dec']:10.6f} "
|
|
424
|
+
f"{row['a']:8.6f} {row['b']:8.6f} {row[theta_colname]:8.6f} "
|
|
425
|
+
"0.0 0.0\n")
|
|
426
|
+
else :
|
|
427
|
+
line = (f"{row['id']:<3} {row['ra']:10.6f} {row['dec']:10.6f} "
|
|
428
|
+
"0.0 0.0 0.0 0.0 0.0\n")
|
|
429
|
+
f.write(line)
|
|
430
|
+
print('Selected sources exported at ' + file_path)
|
|
431
|
+
|
|
432
|
+
|
|
433
|
+
def export_to_potfile(self, file_path=None, units='pixel') :
|
|
434
|
+
cat = self.cat[self.selection_mask] if True in self.selection_mask else self.cat
|
|
435
|
+
|
|
436
|
+
if units=='pixel' :
|
|
437
|
+
cat['a'] *= self.fits_image.pix_deg_scale*3600
|
|
438
|
+
cat['b'] *= self.fits_image.pix_deg_scale*3600
|
|
439
|
+
print("Converting pixel units to arcsec")
|
|
440
|
+
elif units=='deg' :
|
|
441
|
+
cat['a'] *= 3600
|
|
442
|
+
cat['b'] *= 3600
|
|
443
|
+
print("Converting deg units to arcsec")
|
|
444
|
+
else :
|
|
445
|
+
if units!='arcsec' :
|
|
446
|
+
print("Units not recognized, exporting as is")
|
|
447
|
+
|
|
448
|
+
mag_col = self.mag_colnames[0] if self.mag_colnames[0] in cat.colnames else 'mag'
|
|
449
|
+
print(f"Using '{mag_col}' as mag column")
|
|
450
|
+
sort_array = np.argsort(cat[mag_col])
|
|
451
|
+
sorted_cat = cat[sort_array]
|
|
452
|
+
|
|
453
|
+
lines = []
|
|
454
|
+
lines.append('#REFERENCE 0\n')
|
|
455
|
+
lines.append('## id RA Dec a b theta mag lum\n')
|
|
456
|
+
|
|
457
|
+
for i, galaxy in enumerate(sorted_cat) :
|
|
458
|
+
lines.append( '%d %f %f %f %f %f %f 0.\n' % (i+1, galaxy['ra'], galaxy['dec'], galaxy['a'], galaxy['b'], galaxy['theta']-self.fits_image.orientation, galaxy[mag_col]) )
|
|
459
|
+
|
|
460
|
+
if file_path is None :
|
|
461
|
+
if self.ref_path is not None :
|
|
462
|
+
file_path = os.path.join( os.path.dirname(self.ref_path), 'exported_potfile.lenstool')
|
|
463
|
+
else :
|
|
464
|
+
file_path = os.path.join( os.path.dirname(self.fits_image.image_path), 'exported_potfile.lenstool')
|
|
465
|
+
print('Exporting selected sources to ' + file_path)
|
|
466
|
+
with open(file_path, 'w') as file:
|
|
467
|
+
file.writelines(lines)
|
|
468
|
+
|
|
469
|
+
#def transfer_col(self, col_to_transfer) :
|
|
470
|
+
# if self.fits_image.imported_cat is not None :
|
|
471
|
+
# if col_to_transfer in self.fits_image.imported_cat.cat.colnames :
|
|
472
|
+
# temp_cat = match_cat2([self.cat, self.fits_image.imported_cat.cat], keep_all_col=True, fill_in_value=-1)
|
|
473
|
+
# if col_to_transfer in self.cat.colnames :
|
|
474
|
+
# col_to_transfer = col_to_transfer + '_CAT2'
|
|
475
|
+
# self.cat[col_to_transfer] = temp_cat[col_to_transfer]
|
|
476
|
+
# print('###############\nColumn ' + col_to_transfer + ' added.\n###############')
|
|
477
|
+
# else :
|
|
478
|
+
# print(col_to_transfer + ' not found in imported_cat')
|
|
479
|
+
# else :
|
|
480
|
+
# print('No imported_cat')
|
|
481
|
+
|
|
482
|
+
def transfer_col(self, col_to_transfer, which_cat="imported_cat", index=None):
|
|
483
|
+
if index is not None :
|
|
484
|
+
source_cat = self.fits_image.imported_cat_list[index]
|
|
485
|
+
else :
|
|
486
|
+
source_cat = getattr(self.fits_image, which_cat, None)
|
|
487
|
+
|
|
488
|
+
if source_cat is not None:
|
|
489
|
+
if col_to_transfer in source_cat.cat.colnames:
|
|
490
|
+
temp_cat, match_idx = match_cat2([self.cat, source_cat.cat], keep_all_col=True, fill_in_value=-1.0, return_match_idx=True)
|
|
491
|
+
if col_to_transfer in self.cat.colnames:
|
|
492
|
+
col_to_transfer = col_to_transfer + '_CAT2'
|
|
493
|
+
self.cat[col_to_transfer] = temp_cat[col_to_transfer]
|
|
494
|
+
print(f'###############\nColumn {col_to_transfer} added.\n###############')
|
|
495
|
+
else:
|
|
496
|
+
print(f'{col_to_transfer} not found in {which_cat}')
|
|
497
|
+
else:
|
|
498
|
+
print(f'No {which_cat}')
|
|
499
|
+
return match_idx
|
|
500
|
+
|
|
501
|
+
#def export_thumbnails(self, mask=None, group_images=True) :
|
|
502
|
+
# if mask is None :
|
|
503
|
+
# mask = self.selection_mask
|
|
504
|
+
# print('Function is being built.')
|
|
505
|
+
# ### TO DO ###
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
|
|
510
|
+
|
|
511
|
+
|
|
512
|
+
|