teareduce 0.4.9__py3-none-any.whl → 0.5.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.
- teareduce/cleanest/__main__.py +18 -6
- teareduce/cleanest/cosmicraycleanerapp.py +586 -85
- teareduce/cleanest/definitions.py +51 -0
- teareduce/cleanest/find_closest_true.py +44 -0
- teareduce/cleanest/imagedisplay.py +155 -0
- teareduce/cleanest/interpolation_a.py +83 -0
- teareduce/cleanest/interpolation_x.py +80 -0
- teareduce/cleanest/interpolation_y.py +81 -0
- teareduce/cleanest/interpolationeditor.py +207 -0
- teareduce/cleanest/parametereditor.py +251 -0
- teareduce/cleanest/reviewcosmicray.py +351 -267
- teareduce/cookbook/get_cookbook_file.py +5 -0
- teareduce/version.py +1 -1
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/METADATA +3 -1
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/RECORD +19 -11
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/WHEEL +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/entry_points.txt +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.0.dist-info}/top_level.txt +0 -0
|
@@ -10,13 +10,20 @@
|
|
|
10
10
|
"""Define the ReviewCosmicRay class."""
|
|
11
11
|
|
|
12
12
|
import tkinter as tk
|
|
13
|
+
from tkinter import messagebox
|
|
13
14
|
from tkinter import simpledialog
|
|
14
15
|
|
|
15
16
|
import matplotlib.pyplot as plt
|
|
16
17
|
from matplotlib.backend_bases import key_press_handler
|
|
17
18
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
18
19
|
import numpy as np
|
|
19
|
-
from
|
|
20
|
+
from rich import print
|
|
21
|
+
|
|
22
|
+
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
23
|
+
from .imagedisplay import ImageDisplay
|
|
24
|
+
from .interpolation_a import interpolation_a
|
|
25
|
+
from .interpolation_x import interpolation_x
|
|
26
|
+
from .interpolation_y import interpolation_y
|
|
20
27
|
|
|
21
28
|
from ..imshow import imshow
|
|
22
29
|
from ..sliceregion import SliceRegion2D
|
|
@@ -26,74 +33,182 @@ import matplotlib
|
|
|
26
33
|
matplotlib.use("TkAgg")
|
|
27
34
|
|
|
28
35
|
|
|
29
|
-
class ReviewCosmicRay():
|
|
36
|
+
class ReviewCosmicRay(ImageDisplay):
|
|
30
37
|
"""Class to review suspected cosmic ray pixels."""
|
|
31
38
|
|
|
32
|
-
def __init__(self, root, data,
|
|
39
|
+
def __init__(self, root, data, auxdata, cleandata_lacosmic, cr_labels, num_features,
|
|
40
|
+
first_cr_index=1, single_cr=False,
|
|
41
|
+
last_dilation=None, last_npoints=None, last_degree=None):
|
|
33
42
|
"""Initialize the review window.
|
|
34
43
|
|
|
35
44
|
Parameters
|
|
36
45
|
----------
|
|
37
|
-
root : tk.
|
|
38
|
-
The
|
|
46
|
+
root : tk.Toplevel
|
|
47
|
+
The parent Tkinter root window.
|
|
39
48
|
data : 2D numpy array
|
|
40
49
|
The original image data.
|
|
50
|
+
auxdata : 2D numpy array or None
|
|
51
|
+
The auxiliary image data.
|
|
52
|
+
cleandata_lacosmic: 2D numpy array or None
|
|
53
|
+
The cleaned image data from L.A.Cosmic.
|
|
54
|
+
cr_labels : 2D numpy array
|
|
55
|
+
Labels of connected cosmic ray pixel groups.
|
|
56
|
+
num_features : int
|
|
57
|
+
Number of connected cosmic ray pixel groups.
|
|
58
|
+
first_cr_index : int, optional
|
|
59
|
+
The index of the first cosmic ray to review (default is 1).
|
|
60
|
+
single_cr : bool, optional
|
|
61
|
+
Whether to review a single cosmic ray (default is False).
|
|
62
|
+
If True, the review window will close after reviewing the
|
|
63
|
+
selected first cosmic ray.
|
|
64
|
+
last_dilation : int or None, optional
|
|
65
|
+
The last used dilation parameter employed after L.A.Cosmic
|
|
66
|
+
detection. If > 0, the replacement by the L.A.Cosmic cleaned
|
|
67
|
+
data will not be allowed.
|
|
68
|
+
last_npoints : int or None, optional
|
|
69
|
+
The last used number of points parameter for interpolation.
|
|
70
|
+
last_degree : int or None, optional
|
|
71
|
+
The last used degree parameter for interpolation.
|
|
72
|
+
|
|
73
|
+
Methods
|
|
74
|
+
-------
|
|
75
|
+
create_widgets()
|
|
76
|
+
Create the GUI widgets for the review window.
|
|
77
|
+
update_display(cleaned=False)
|
|
78
|
+
Update the display to show the current cosmic ray.
|
|
79
|
+
set_ndeg()
|
|
80
|
+
Set the Npoints and Degree parameters for interpolation.
|
|
81
|
+
interp_x()
|
|
82
|
+
Perform X-interpolation for the current cosmic ray.
|
|
83
|
+
interp_y()
|
|
84
|
+
Perform Y-interpolation for the current cosmic ray.
|
|
85
|
+
interp_a(method)
|
|
86
|
+
Perform interpolation using the specified method for the current cosmic ray.
|
|
87
|
+
use_lacosmic()
|
|
88
|
+
Replace cosmic ray pixels with L.A.Cosmic cleaned data.
|
|
89
|
+
use_auxdata()
|
|
90
|
+
Replace cosmic ray pixels with auxiliary data.
|
|
91
|
+
remove_crosses()
|
|
92
|
+
Remove all pixels of the current cosmic ray from the review.
|
|
93
|
+
restore_cr()
|
|
94
|
+
Restore all pixels of the current cosmic ray to their original values.
|
|
95
|
+
continue_cr()
|
|
96
|
+
Move to the next cosmic ray for review.
|
|
97
|
+
exit_review()
|
|
98
|
+
Close the review window.
|
|
99
|
+
on_key(event)
|
|
100
|
+
Handle key press events for shortcuts.
|
|
101
|
+
on_click(event)
|
|
102
|
+
Handle mouse click events to mark/unmark pixels as cosmic rays.
|
|
103
|
+
|
|
104
|
+
Attributes
|
|
105
|
+
----------
|
|
106
|
+
root : tk.Toplevel
|
|
107
|
+
The parent Tkinter root window.
|
|
108
|
+
data : 2D numpy array
|
|
109
|
+
The original image data.
|
|
110
|
+
auxdata : 2D numpy array or None
|
|
111
|
+
The auxiliary image data.
|
|
112
|
+
cleandata_lacosmic: 2D numpy array or None
|
|
113
|
+
The cleaned image data from L.A.Cosmic.
|
|
114
|
+
cr_labels : 2D numpy array
|
|
115
|
+
Labels of connected cosmic ray pixel groups.
|
|
116
|
+
num_features : int
|
|
117
|
+
Number of connected cosmic ray pixel groups.
|
|
118
|
+
num_cr_cleaned : int
|
|
119
|
+
Number of cosmic rays cleaned during the review.
|
|
41
120
|
mask_fixed : 2D numpy array
|
|
42
|
-
Mask of
|
|
43
|
-
|
|
44
|
-
|
|
121
|
+
Mask of pixels fixed during the review.
|
|
122
|
+
first_plot : bool
|
|
123
|
+
Flag to indicate if it's the first plot.
|
|
124
|
+
degree : int
|
|
125
|
+
Degree parameter for interpolation.
|
|
126
|
+
npoints : int
|
|
127
|
+
Number of points parameter for interpolation.
|
|
128
|
+
last_dilation : int or None
|
|
129
|
+
The last used dilation parameter employed after L.A.Cosmic
|
|
130
|
+
detection.
|
|
45
131
|
"""
|
|
46
132
|
self.root = root
|
|
133
|
+
self.root.title("Review Cosmic Rays")
|
|
134
|
+
self.auxdata = auxdata
|
|
135
|
+
if self.auxdata is not None:
|
|
136
|
+
self.root.geometry("1200x700+100+100")
|
|
137
|
+
else:
|
|
138
|
+
self.root.geometry("800x700+100+100")
|
|
47
139
|
self.data = data
|
|
140
|
+
self.cleandata_lacosmic = cleandata_lacosmic
|
|
48
141
|
self.data_original = data.copy()
|
|
49
|
-
self.
|
|
50
|
-
self.
|
|
142
|
+
self.cr_labels = cr_labels
|
|
143
|
+
self.num_features = num_features
|
|
144
|
+
self.num_cr_cleaned = 0
|
|
145
|
+
self.mask_fixed = np.zeros(self.data.shape, dtype=bool) # Mask of pixels fixed during review
|
|
51
146
|
self.first_plot = True
|
|
52
|
-
self.degree =
|
|
53
|
-
self.npoints =
|
|
54
|
-
|
|
55
|
-
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
56
|
-
# diagonal connections too, so we define a 3x3 square.
|
|
57
|
-
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
58
|
-
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
147
|
+
self.degree = last_degree if last_degree is not None else 1
|
|
148
|
+
self.npoints = last_npoints if last_npoints is not None else 2
|
|
149
|
+
self.last_dilation = last_dilation
|
|
59
150
|
# Make a copy of the original labels to allow pixel re-marking
|
|
60
151
|
self.cr_labels_original = self.cr_labels.copy()
|
|
61
|
-
print(f"Number of cosmic ray pixels detected
|
|
62
|
-
print(f"Number of cosmic rays
|
|
152
|
+
print(f"Number of cosmic ray pixels detected..: {np.sum(self.cr_labels > 0)}")
|
|
153
|
+
print(f"Number of cosmic rays (grouped pixels): {self.num_features}")
|
|
63
154
|
if self.num_features == 0:
|
|
64
155
|
print('No CR hits found!')
|
|
65
156
|
else:
|
|
66
|
-
self.cr_index =
|
|
157
|
+
self.cr_index = first_cr_index
|
|
158
|
+
self.single_cr = single_cr
|
|
67
159
|
self.create_widgets()
|
|
68
160
|
|
|
69
161
|
def create_widgets(self):
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
self.
|
|
73
|
-
|
|
74
|
-
self.button_frame1 = tk.Frame(self.review_window)
|
|
162
|
+
"""Create the GUI widgets for the review window."""
|
|
163
|
+
# Row 1 of buttons
|
|
164
|
+
self.button_frame1 = tk.Frame(self.root)
|
|
75
165
|
self.button_frame1.pack(pady=5)
|
|
76
|
-
self.
|
|
166
|
+
self.ndeg_label = tk.Button(self.button_frame1, text=f"Npoints={self.npoints}, Degree={self.degree}",
|
|
167
|
+
command=self.set_ndeg)
|
|
168
|
+
self.ndeg_label.pack(side=tk.LEFT, padx=5)
|
|
169
|
+
self.remove_crosses_button = tk.Button(self.button_frame1, text="remove all x's", command=self.remove_crosses)
|
|
77
170
|
self.remove_crosses_button.pack(side=tk.LEFT, padx=5)
|
|
78
|
-
self.restore_cr_button = tk.Button(self.button_frame1, text="[r]estore CR", command=self.restore_cr)
|
|
171
|
+
self.restore_cr_button = tk.Button(self.button_frame1, text="[r]estore CR data", command=self.restore_cr)
|
|
79
172
|
self.restore_cr_button.pack(side=tk.LEFT, padx=5)
|
|
80
173
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
81
174
|
self.next_button = tk.Button(self.button_frame1, text="[c]ontinue", command=self.continue_cr)
|
|
82
175
|
self.next_button.pack(side=tk.LEFT, padx=5)
|
|
176
|
+
self.exit_button = tk.Button(self.button_frame1, text="[e]xit review", command=self.exit_review)
|
|
177
|
+
self.exit_button.pack(side=tk.LEFT, padx=5)
|
|
83
178
|
|
|
84
|
-
|
|
179
|
+
# Row 2 of buttons
|
|
180
|
+
self.button_frame2 = tk.Frame(self.root)
|
|
85
181
|
self.button_frame2.pack(pady=5)
|
|
86
|
-
self.ndeg_label = tk.Button(self.button_frame2, text=f"deg={self.degree}, n={self.npoints}",
|
|
87
|
-
command=self.set_ndeg)
|
|
88
|
-
self.ndeg_label.pack(side=tk.LEFT, padx=5)
|
|
89
182
|
self.interp_x_button = tk.Button(self.button_frame2, text="[x] interp.", command=self.interp_x)
|
|
90
183
|
self.interp_x_button.pack(side=tk.LEFT, padx=5)
|
|
91
184
|
self.interp_y_button = tk.Button(self.button_frame2, text="[y] interp.", command=self.interp_y)
|
|
92
185
|
self.interp_y_button.pack(side=tk.LEFT, padx=5)
|
|
93
|
-
|
|
186
|
+
# it is important to use lambda here to pass the method argument correctly
|
|
187
|
+
# (avoiding the execution of the function at button creation time, which would happen
|
|
188
|
+
# if we didn't use lambda; in that case, the function would be called immediately and
|
|
189
|
+
# its return value (None) would be assigned to the command parameter; furthermore,
|
|
190
|
+
# the function is trying to deactivate the buttons before they are created, which
|
|
191
|
+
# would lead to an error; in addition, since I have two buttons calling the same function
|
|
192
|
+
# with different arguments, using lambda allows to differentiate them)
|
|
193
|
+
self.interp_s_button = tk.Button(self.button_frame2, text="[s]urface interp.",
|
|
194
|
+
command=lambda: self.interp_a('surface'))
|
|
94
195
|
self.interp_s_button.pack(side=tk.LEFT, padx=5)
|
|
95
|
-
|
|
96
|
-
|
|
196
|
+
self.interp_m_button = tk.Button(self.button_frame2, text="[m]edian",
|
|
197
|
+
command=lambda: self.interp_a('median'))
|
|
198
|
+
self.interp_m_button.pack(side=tk.LEFT, padx=5)
|
|
199
|
+
self.interp_l_button = tk.Button(self.button_frame2, text="[l]acosmic", command=self.use_lacosmic)
|
|
200
|
+
self.interp_l_button.pack(side=tk.LEFT, padx=5)
|
|
201
|
+
if self.last_dilation is not None and self.last_dilation > 0:
|
|
202
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
203
|
+
if self.cleandata_lacosmic is None:
|
|
204
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
205
|
+
self.interp_aux_button = tk.Button(self.button_frame2, text="[a]ux. data", command=self.use_auxdata)
|
|
206
|
+
self.interp_aux_button.pack(side=tk.LEFT, padx=5)
|
|
207
|
+
if self.auxdata is None:
|
|
208
|
+
self.interp_aux_button.config(state=tk.DISABLED)
|
|
209
|
+
|
|
210
|
+
# Row 3 of buttons
|
|
211
|
+
self.button_frame3 = tk.Frame(self.root)
|
|
97
212
|
self.button_frame3.pack(pady=5)
|
|
98
213
|
vmin, vmax = zscale(self.data)
|
|
99
214
|
self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
|
|
@@ -104,11 +219,15 @@ class ReviewCosmicRay():
|
|
|
104
219
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
105
220
|
self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
|
|
106
221
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
107
|
-
self.exit_button = tk.Button(self.button_frame3, text="[e]xit review", command=self.exit_review)
|
|
108
|
-
self.exit_button.pack(side=tk.LEFT, padx=5)
|
|
109
222
|
|
|
110
|
-
|
|
111
|
-
self.
|
|
223
|
+
# Figure
|
|
224
|
+
if self.auxdata is not None:
|
|
225
|
+
self.fig, (self.ax_aux, self.ax) = plt.subplots(
|
|
226
|
+
ncols=2, figsize=(10, 5), constrained_layout=True)
|
|
227
|
+
else:
|
|
228
|
+
self.fig, self.ax = plt.subplots(figsize=(8, 5), constrained_layout=True)
|
|
229
|
+
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
|
|
230
|
+
self.canvas.get_tk_widget().pack(padx=5, pady=5)
|
|
112
231
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
113
232
|
self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
|
|
114
233
|
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
@@ -117,286 +236,206 @@ class ReviewCosmicRay():
|
|
|
117
236
|
self.canvas_widget.pack(fill=tk.BOTH, expand=True)
|
|
118
237
|
|
|
119
238
|
# Matplotlib toolbar
|
|
120
|
-
self.toolbar_frame = tk.Frame(self.
|
|
239
|
+
self.toolbar_frame = tk.Frame(self.root)
|
|
121
240
|
self.toolbar_frame.pack(fill=tk.X, expand=False, pady=5)
|
|
122
241
|
self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
|
|
123
242
|
self.toolbar.update()
|
|
124
243
|
|
|
125
244
|
self.update_display()
|
|
126
245
|
|
|
127
|
-
|
|
246
|
+
def update_display(self, cleaned=False):
|
|
247
|
+
"""Update the display to show the current cosmic ray.
|
|
128
248
|
|
|
129
|
-
|
|
249
|
+
Parameters
|
|
250
|
+
----------
|
|
251
|
+
cleaned : bool, optional
|
|
252
|
+
Whether the cosmic ray has been cleaned (default is False).
|
|
253
|
+
If True, the cosmic ray pixels will be marked differently.
|
|
254
|
+
"""
|
|
130
255
|
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
131
256
|
ycr_list_original, xcr_list_original = np.where(self.cr_labels_original == self.cr_index)
|
|
132
257
|
if self.first_plot:
|
|
133
258
|
print(f"Cosmic ray {self.cr_index}: "
|
|
134
259
|
f"Number of pixels = {len(xcr_list)}, "
|
|
135
|
-
f"Centroid = ({np.mean(xcr_list):.2f}, {np.mean(ycr_list):.2f})")
|
|
260
|
+
f"Centroid = ({np.mean(xcr_list)+1:.2f}, {np.mean(ycr_list)+1:.2f})")
|
|
136
261
|
# Use original positions to define the region to display in order
|
|
137
262
|
# to avoid image shifts when some pixels are unmarked or new ones are marked
|
|
138
263
|
i0 = int(np.mean(ycr_list_original) + 0.5)
|
|
139
264
|
j0 = int(np.mean(xcr_list_original) + 0.5)
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
265
|
+
semiwidth = MAX_PIXEL_DISTANCE_TO_CR
|
|
266
|
+
jmin = j0 - semiwidth if j0 - semiwidth >= 0 else 0
|
|
267
|
+
jmax = j0 + semiwidth if j0 + semiwidth < self.data.shape[1] else self.data.shape[1] - 1
|
|
268
|
+
imin = i0 - semiwidth if i0 - semiwidth >= 0 else 0
|
|
269
|
+
imax = i0 + semiwidth if i0 + semiwidth < self.data.shape[0] else self.data.shape[0] - 1
|
|
270
|
+
# Force the region to be of size (2*semiwidth + 1) x (2*semiwidth + 1)
|
|
271
|
+
if jmin == 0:
|
|
272
|
+
jmax = 2 * semiwidth
|
|
273
|
+
elif jmax == self.data.shape[1] - 1:
|
|
274
|
+
jmin = self.data.shape[1] - 1 - 2 * semiwidth
|
|
275
|
+
if imin == 0:
|
|
276
|
+
imax = 2 * semiwidth
|
|
277
|
+
elif imax == self.data.shape[0] - 1:
|
|
278
|
+
imin = self.data.shape[0] - 1 - 2 * semiwidth
|
|
144
279
|
self.region = SliceRegion2D(f'[{jmin+1}:{jmax+1}, {imin+1}:{imax+1}]', mode='fits').python
|
|
145
280
|
self.ax.clear()
|
|
146
281
|
vmin = self.get_vmin()
|
|
147
282
|
vmax = self.get_vmax()
|
|
148
283
|
xlabel = 'X pixel (from 1 to NAXIS1)'
|
|
149
284
|
ylabel = 'Y pixel (from 1 to NAXIS2)'
|
|
150
|
-
self.
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
self.
|
|
285
|
+
self.image, _, _ = imshow(self.fig, self.ax, self.data[self.region], colorbar=False,
|
|
286
|
+
xlabel=xlabel, ylabel=ylabel,
|
|
287
|
+
vmin=vmin, vmax=vmax)
|
|
288
|
+
self.image.set_extent([jmin + 0.5, jmax + 1.5, imin + 0.5, imax + 1.5])
|
|
289
|
+
if self.auxdata is not None:
|
|
290
|
+
self.ax_aux.clear()
|
|
291
|
+
self.image_aux, _, _ = imshow(self.fig, self.ax_aux, self.auxdata[self.region],
|
|
292
|
+
colorbar=False,
|
|
293
|
+
xlabel=xlabel, ylabel=ylabel,
|
|
294
|
+
vmin=vmin, vmax=vmax)
|
|
295
|
+
self.image_aux.set_extent([jmin + 0.5, jmax + 1.5, imin + 0.5, imax + 1.5])
|
|
296
|
+
self.ax_aux.set_title("Auxiliary data")
|
|
297
|
+
# Overplot cosmic ray pixels
|
|
154
298
|
xlim = self.ax.get_xlim()
|
|
155
299
|
ylim = self.ax.get_ylim()
|
|
156
300
|
for xcr, ycr in zip(xcr_list, ycr_list):
|
|
157
301
|
xcr += 1 # from index to pixel
|
|
158
302
|
ycr += 1 # from index to pixel
|
|
159
|
-
|
|
160
|
-
|
|
303
|
+
if cleaned:
|
|
304
|
+
self.ax.plot(xcr, ycr, 'C1o', markersize=4)
|
|
305
|
+
else:
|
|
306
|
+
self.ax.plot([xcr - 0.5, xcr + 0.5], [ycr + 0.5, ycr - 0.5], 'r-')
|
|
307
|
+
self.ax.plot([xcr - 0.5, xcr + 0.5], [ycr - 0.5, ycr + 0.5], 'r-')
|
|
161
308
|
self.ax.set_xlim(xlim)
|
|
162
309
|
self.ax.set_ylim(ylim)
|
|
163
310
|
self.ax.set_title(f"Cosmic ray #{self.cr_index}/{self.num_features}")
|
|
164
311
|
if self.first_plot:
|
|
165
312
|
self.first_plot = False
|
|
166
|
-
|
|
167
|
-
self.canvas.draw()
|
|
168
|
-
|
|
169
|
-
def set_vmin(self):
|
|
170
|
-
old_vmin = self.get_vmin()
|
|
171
|
-
new_vmin = simpledialog.askfloat("Set vmin", "Enter new vmin:", initialvalue=old_vmin)
|
|
172
|
-
if new_vmin is None:
|
|
173
|
-
return
|
|
174
|
-
self.vmin_button.config(text=f"vmin: {new_vmin:.2f}")
|
|
175
|
-
self.image_review.set_clim(vmin=new_vmin)
|
|
176
|
-
self.canvas.draw()
|
|
177
|
-
|
|
178
|
-
def set_vmax(self):
|
|
179
|
-
old_vmax = self.get_vmax()
|
|
180
|
-
new_vmax = simpledialog.askfloat("Set vmax", "Enter new vmax:", initialvalue=old_vmax)
|
|
181
|
-
if new_vmax is None:
|
|
182
|
-
return
|
|
183
|
-
self.vmax_button.config(text=f"vmax: {new_vmax:.2f}")
|
|
184
|
-
self.image_review.set_clim(vmax=new_vmax)
|
|
185
|
-
self.canvas.draw()
|
|
186
|
-
|
|
187
|
-
def get_vmin(self):
|
|
188
|
-
return float(self.vmin_button.cget("text").split(":")[1])
|
|
189
|
-
|
|
190
|
-
def get_vmax(self):
|
|
191
|
-
return float(self.vmax_button.cget("text").split(":")[1])
|
|
192
|
-
|
|
193
|
-
def set_minmax(self):
|
|
194
|
-
vmin_new = np.min(self.data[self.region])
|
|
195
|
-
vmax_new = np.max(self.data[self.region])
|
|
196
|
-
self.vmin_button.config(text=f"vmin: {vmin_new:.2f}")
|
|
197
|
-
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
198
|
-
self.image_review.set_clim(vmin=vmin_new)
|
|
199
|
-
self.image_review.set_clim(vmax=vmax_new)
|
|
200
|
-
self.canvas.draw()
|
|
201
|
-
|
|
202
|
-
def set_zscale(self):
|
|
203
|
-
vmin_new, vmax_new = zscale(self.data[self.region])
|
|
204
|
-
self.vmin_button.config(text=f"vmin: {vmin_new:.2f}")
|
|
205
|
-
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
206
|
-
self.image_review.set_clim(vmin=vmin_new)
|
|
207
|
-
self.image_review.set_clim(vmax=vmax_new)
|
|
208
|
-
self.canvas.draw()
|
|
313
|
+
self.canvas.draw_idle()
|
|
209
314
|
|
|
210
315
|
def set_ndeg(self):
|
|
211
|
-
|
|
316
|
+
"""Set the number of points and degree for interpolation."""
|
|
317
|
+
new_npoints = simpledialog.askinteger("Set Npoints", "Enter Npoints:",
|
|
318
|
+
initialvalue=self.npoints, minvalue=1)
|
|
319
|
+
if new_npoints is None:
|
|
320
|
+
return
|
|
321
|
+
new_degree = simpledialog.askinteger("Set degree", "Enter Degree (min=0):",
|
|
212
322
|
initialvalue=self.degree, minvalue=0)
|
|
213
323
|
if new_degree is None:
|
|
214
324
|
return
|
|
215
|
-
new_npoints = simpledialog.askinteger("Set n", f"Enter new n (min={2*new_degree}):",
|
|
216
|
-
initialvalue=self.npoints, minvalue=2*new_degree)
|
|
217
|
-
if new_npoints is None:
|
|
218
|
-
return
|
|
219
325
|
self.degree = new_degree
|
|
220
326
|
self.npoints = new_npoints
|
|
221
|
-
self.ndeg_label.config(text=f"
|
|
327
|
+
self.ndeg_label.config(text=f"Npoints={self.npoints}, Degree={self.degree}")
|
|
222
328
|
|
|
223
|
-
def
|
|
224
|
-
|
|
225
|
-
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
226
|
-
ycr_min = np.min(ycr_list)
|
|
227
|
-
ycr_max = np.max(ycr_list)
|
|
228
|
-
xfit_all = []
|
|
229
|
-
yfit_all = []
|
|
230
|
-
for ycr in range(ycr_min, ycr_max + 1):
|
|
231
|
-
xmarked = xcr_list[np.where(ycr_list == ycr)]
|
|
232
|
-
if len(xmarked) > 0:
|
|
233
|
-
jmin = np.min(xmarked)
|
|
234
|
-
jmax = np.max(xmarked)
|
|
235
|
-
# mark intermediate pixels too
|
|
236
|
-
for ix in range(jmin, jmax + 1):
|
|
237
|
-
self.cr_labels[ycr, ix] = self.cr_index
|
|
238
|
-
xmarked = xcr_list[np.where(ycr_list == ycr)]
|
|
239
|
-
xfit = []
|
|
240
|
-
zfit = []
|
|
241
|
-
for i in range(jmin - self.npoints, jmin):
|
|
242
|
-
if 0 <= i < self.data.shape[1]:
|
|
243
|
-
xfit.append(i)
|
|
244
|
-
xfit_all.append(i)
|
|
245
|
-
yfit_all.append(ycr)
|
|
246
|
-
zfit.append(self.data[ycr, i])
|
|
247
|
-
for i in range(jmax + 1, jmax + 1 + self.npoints):
|
|
248
|
-
if 0 <= i < self.data.shape[1]:
|
|
249
|
-
xfit.append(i)
|
|
250
|
-
xfit_all.append(i)
|
|
251
|
-
yfit_all.append(ycr)
|
|
252
|
-
zfit.append(self.data[ycr, i])
|
|
253
|
-
if len(xfit) > self.degree:
|
|
254
|
-
p = np.polyfit(xfit, zfit, self.degree)
|
|
255
|
-
for i in range(jmin, jmax + 1):
|
|
256
|
-
if 0 <= i < self.data.shape[1]:
|
|
257
|
-
self.data[ycr, i] = np.polyval(p, i)
|
|
258
|
-
self.mask_fixed[ycr, i] = True
|
|
259
|
-
else:
|
|
260
|
-
print(f"Not enough points to fit at y={ycr+1}")
|
|
261
|
-
self.update_display()
|
|
262
|
-
return
|
|
329
|
+
def set_buttons_after_cleaning_cr(self):
|
|
330
|
+
"""Set the state of buttons after cleaning a cosmic ray."""
|
|
263
331
|
self.restore_cr_button.config(state=tk.NORMAL)
|
|
264
332
|
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
265
333
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
266
334
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
267
335
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
268
|
-
self.
|
|
336
|
+
self.interp_m_button.config(state=tk.DISABLED)
|
|
337
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
338
|
+
self.interp_aux_button.config(state=tk.DISABLED)
|
|
339
|
+
|
|
340
|
+
def interp_x(self):
|
|
341
|
+
"""Perform x-direction interpolation to clean a cosmic ray."""
|
|
342
|
+
if 2 * self.npoints <= self.degree:
|
|
343
|
+
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x interpolation.")
|
|
344
|
+
return
|
|
345
|
+
print(f"X-interpolation of cosmic ray {self.cr_index}")
|
|
346
|
+
interpolation_performed, xfit_all, yfit_all = interpolation_x(
|
|
347
|
+
data=self.data,
|
|
348
|
+
mask_fixed=self.mask_fixed,
|
|
349
|
+
cr_labels=self.cr_labels,
|
|
350
|
+
cr_index=self.cr_index,
|
|
351
|
+
npoints=self.npoints,
|
|
352
|
+
degree=self.degree
|
|
353
|
+
)
|
|
354
|
+
if interpolation_performed:
|
|
355
|
+
self.num_cr_cleaned += 1
|
|
356
|
+
self.set_buttons_after_cleaning_cr()
|
|
357
|
+
self.update_display(cleaned=interpolation_performed)
|
|
269
358
|
if len(xfit_all) > 0:
|
|
270
359
|
self.ax.plot(np.array(xfit_all) + 1, np.array(yfit_all) + 1, 'mo', markersize=4) # +1: from index to pixel
|
|
271
|
-
self.canvas.
|
|
360
|
+
self.canvas.draw_idle()
|
|
272
361
|
|
|
273
362
|
def interp_y(self):
|
|
363
|
+
"""Perform y-direction interpolation to clean a cosmic ray."""
|
|
364
|
+
if 2 * self.npoints <= self.degree:
|
|
365
|
+
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for y interpolation.")
|
|
366
|
+
return
|
|
274
367
|
print(f"Y-interpolation of cosmic ray {self.cr_index}")
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
self.cr_labels[iy, xcr] = self.cr_index
|
|
288
|
-
ymarked = ycr_list[np.where(xcr_list == xcr)]
|
|
289
|
-
yfit = []
|
|
290
|
-
zfit = []
|
|
291
|
-
for i in range(imin - self.npoints, imin):
|
|
292
|
-
if 0 <= i < self.data.shape[0]:
|
|
293
|
-
yfit.append(i)
|
|
294
|
-
yfit_all.append(i)
|
|
295
|
-
xfit_all.append(xcr)
|
|
296
|
-
zfit.append(self.data[i, xcr])
|
|
297
|
-
for i in range(imax + 1, imax + 1 + self.npoints):
|
|
298
|
-
if 0 <= i < self.data.shape[0]:
|
|
299
|
-
yfit.append(i)
|
|
300
|
-
yfit_all.append(i)
|
|
301
|
-
xfit_all.append(xcr)
|
|
302
|
-
zfit.append(self.data[i, xcr])
|
|
303
|
-
if len(yfit) > self.degree:
|
|
304
|
-
p = np.polyfit(yfit, zfit, self.degree)
|
|
305
|
-
for i in range(imin, imax + 1):
|
|
306
|
-
if 0 <= i < self.data.shape[1]:
|
|
307
|
-
self.data[i, xcr] = np.polyval(p, i)
|
|
308
|
-
self.mask_fixed[i, xcr] = True
|
|
309
|
-
else:
|
|
310
|
-
print(f"Not enough points to fit at x={xcr+1}")
|
|
311
|
-
self.update_display()
|
|
312
|
-
return
|
|
313
|
-
self.restore_cr_button.config(state=tk.NORMAL)
|
|
314
|
-
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
315
|
-
self.interp_x_button.config(state=tk.DISABLED)
|
|
316
|
-
self.interp_y_button.config(state=tk.DISABLED)
|
|
317
|
-
self.interp_s_button.config(state=tk.DISABLED)
|
|
318
|
-
self.update_display()
|
|
368
|
+
interpolation_performed, xfit_all, yfit_all = interpolation_y(
|
|
369
|
+
data=self.data,
|
|
370
|
+
mask_fixed=self.mask_fixed,
|
|
371
|
+
cr_labels=self.cr_labels,
|
|
372
|
+
cr_index=self.cr_index,
|
|
373
|
+
npoints=self.npoints,
|
|
374
|
+
degree=self.degree
|
|
375
|
+
)
|
|
376
|
+
if interpolation_performed:
|
|
377
|
+
self.num_cr_cleaned += 1
|
|
378
|
+
self.set_buttons_after_cleaning_cr()
|
|
379
|
+
self.update_display(cleaned=interpolation_performed)
|
|
319
380
|
if len(xfit_all) > 0:
|
|
320
381
|
self.ax.plot(np.array(xfit_all) + 1, np.array(yfit_all) + 1, 'mo', markersize=4) # +1: from index to pixel
|
|
321
|
-
self.canvas.
|
|
382
|
+
self.canvas.draw_idle()
|
|
322
383
|
|
|
323
|
-
def
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
yfit_all.append(ycr)
|
|
345
|
-
zfit_all.append(self.data[ycr, i])
|
|
346
|
-
for i in range(jmax + 1, jmax + 1 + self.npoints):
|
|
347
|
-
if 0 <= i < self.data.shape[1]:
|
|
348
|
-
xfit_all.append(i)
|
|
349
|
-
yfit_all.append(ycr)
|
|
350
|
-
zfit_all.append(self.data[ycr, i])
|
|
351
|
-
xcr_min = np.min(xcr_list)
|
|
352
|
-
# Now do vertical lines
|
|
353
|
-
xcr_max = np.max(xcr_list)
|
|
354
|
-
for xcr in range(xcr_min, xcr_max + 1):
|
|
355
|
-
ymarked = ycr_list[np.where(xcr_list == xcr)]
|
|
356
|
-
if len(ymarked) > 0:
|
|
357
|
-
imin = np.min(ymarked)
|
|
358
|
-
imax = np.max(ymarked)
|
|
359
|
-
# mark intermediate pixels too
|
|
360
|
-
for iy in range(imin, imax + 1):
|
|
361
|
-
self.cr_labels[iy, xcr] = self.cr_index
|
|
362
|
-
ymarked = ycr_list[np.where(xcr_list == xcr)]
|
|
363
|
-
for i in range(imin - self.npoints, imin):
|
|
364
|
-
if 0 <= i < self.data.shape[0]:
|
|
365
|
-
yfit_all.append(i)
|
|
366
|
-
xfit_all.append(xcr)
|
|
367
|
-
zfit_all.append(self.data[i, xcr])
|
|
368
|
-
for i in range(imax + 1, imax + 1 + self.npoints):
|
|
369
|
-
if 0 <= i < self.data.shape[0]:
|
|
370
|
-
yfit_all.append(i)
|
|
371
|
-
xfit_all.append(xcr)
|
|
372
|
-
zfit_all.append(self.data[i, xcr])
|
|
373
|
-
if len(xfit_all) > 3:
|
|
374
|
-
# Construct the design matrix for a 2D polynomial fit to a plane,
|
|
375
|
-
# where each row corresponds to a point (x, y, z) and the model
|
|
376
|
-
# is z = C[0]*x + C[1]*y + C[2]
|
|
377
|
-
A = np.c_[xfit_all, yfit_all, np.ones(len(xfit_all))]
|
|
378
|
-
# Least squares polynomial fit
|
|
379
|
-
C, _, _, _ = np.linalg.lstsq(A, zfit_all, rcond=None)
|
|
380
|
-
# recompute all CR pixels to take into account "holes" between marked pixels
|
|
381
|
-
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
382
|
-
for iy, ix in zip(ycr_list, xcr_list):
|
|
383
|
-
self.data[iy, ix] = C[0] * ix + C[1] * iy + C[2]
|
|
384
|
-
self.mask_fixed[iy, ix] = True
|
|
385
|
-
else:
|
|
386
|
-
print("Not enough points to fit a plane")
|
|
387
|
-
self.update_display()
|
|
388
|
-
return
|
|
389
|
-
self.restore_cr_button.config(state=tk.NORMAL)
|
|
390
|
-
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
391
|
-
self.interp_x_button.config(state=tk.DISABLED)
|
|
392
|
-
self.interp_y_button.config(state=tk.DISABLED)
|
|
393
|
-
self.interp_s_button.config(state=tk.DISABLED)
|
|
394
|
-
self.update_display()
|
|
384
|
+
def interp_a(self, method):
|
|
385
|
+
"""Perform interpolation using the specified method to clean a cosmic ray.
|
|
386
|
+
|
|
387
|
+
Parameters
|
|
388
|
+
----------
|
|
389
|
+
method : str
|
|
390
|
+
The interpolation method to use ('surface' or 'median').
|
|
391
|
+
"""
|
|
392
|
+
print(f"{method} interpolation of cosmic ray {self.cr_index}")
|
|
393
|
+
interpolation_performed, xfit_all, yfit_all = interpolation_a(
|
|
394
|
+
data=self.data,
|
|
395
|
+
mask_fixed=self.mask_fixed,
|
|
396
|
+
cr_labels=self.cr_labels,
|
|
397
|
+
cr_index=self.cr_index,
|
|
398
|
+
npoints=self.npoints,
|
|
399
|
+
method=method
|
|
400
|
+
)
|
|
401
|
+
if interpolation_performed:
|
|
402
|
+
self.num_cr_cleaned += 1
|
|
403
|
+
self.set_buttons_after_cleaning_cr()
|
|
404
|
+
self.update_display(cleaned=interpolation_performed)
|
|
395
405
|
if len(xfit_all) > 0:
|
|
396
406
|
self.ax.plot(np.array(xfit_all) + 1, np.array(yfit_all) + 1, 'mo', markersize=4) # +1: from index to pixel
|
|
397
|
-
self.canvas.
|
|
407
|
+
self.canvas.draw_idle()
|
|
408
|
+
|
|
409
|
+
def use_lacosmic(self):
|
|
410
|
+
"""Use L.A.Cosmic cleaned data to clean a cosmic ray."""
|
|
411
|
+
if self.cleandata_lacosmic is None:
|
|
412
|
+
print("L.A.Cosmic cleaned data not available.")
|
|
413
|
+
return
|
|
414
|
+
print(f"L.A.Cosmic interpolation of cosmic ray {self.cr_index}")
|
|
415
|
+
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
416
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
417
|
+
self.data[iy, ix] = self.cleandata_lacosmic[iy, ix]
|
|
418
|
+
self.mask_fixed[iy, ix] = True
|
|
419
|
+
self.num_cr_cleaned += 1
|
|
420
|
+
self.set_buttons_after_cleaning_cr()
|
|
421
|
+
self.update_display(cleaned=True)
|
|
422
|
+
|
|
423
|
+
def use_auxdata(self):
|
|
424
|
+
"""Use auxiliary data to clean a cosmic ray."""
|
|
425
|
+
if self.auxdata is None:
|
|
426
|
+
print("Auxiliary data not available.")
|
|
427
|
+
return
|
|
428
|
+
print(f"Auxiliary data interpolation of cosmic ray {self.cr_index}")
|
|
429
|
+
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
430
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
431
|
+
self.data[iy, ix] = self.auxdata[iy, ix]
|
|
432
|
+
self.mask_fixed[iy, ix] = True
|
|
433
|
+
self.num_cr_cleaned += 1
|
|
434
|
+
self.set_buttons_after_cleaning_cr()
|
|
435
|
+
self.update_display(cleaned=True)
|
|
398
436
|
|
|
399
437
|
def remove_crosses(self):
|
|
438
|
+
"""Remove all pixels of the current cosmic ray from the review."""
|
|
400
439
|
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
401
440
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
402
441
|
self.cr_labels[iy, ix] = 0
|
|
@@ -405,35 +444,61 @@ class ReviewCosmicRay():
|
|
|
405
444
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
406
445
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
407
446
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
447
|
+
self.interp_m_button.config(state=tk.DISABLED)
|
|
448
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
449
|
+
self.interp_aux_button.config(state=tk.DISABLED)
|
|
408
450
|
self.update_display()
|
|
409
451
|
|
|
410
452
|
def restore_cr(self):
|
|
453
|
+
"""Restore all pixels of the current cosmic ray to their original values."""
|
|
411
454
|
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
412
455
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
413
456
|
self.data[iy, ix] = self.data_original[iy, ix]
|
|
457
|
+
self.mask_fixed[iy, ix] = False
|
|
414
458
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
415
459
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
416
460
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
461
|
+
self.interp_m_button.config(state=tk.NORMAL)
|
|
462
|
+
if self.cleandata_lacosmic is not None:
|
|
463
|
+
if self.last_dilation is None or self.last_dilation == 0:
|
|
464
|
+
self.interp_l_button.config(state=tk.NORMAL)
|
|
465
|
+
if self.auxdata is not None:
|
|
466
|
+
self.interp_aux_button.config(state=tk.NORMAL)
|
|
417
467
|
print(f"Restored all pixels of cosmic ray {self.cr_index}")
|
|
468
|
+
self.num_cr_cleaned -= 1
|
|
418
469
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
419
470
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
420
471
|
self.update_display()
|
|
421
472
|
|
|
422
473
|
def continue_cr(self):
|
|
474
|
+
"""Move to the next cosmic ray for review."""
|
|
475
|
+
if self.single_cr:
|
|
476
|
+
self.exit_review()
|
|
477
|
+
return # important: do not remove (to avoid errors)
|
|
423
478
|
self.cr_index += 1
|
|
424
479
|
if self.cr_index > self.num_features:
|
|
425
|
-
self.
|
|
480
|
+
self.exit_review()
|
|
481
|
+
return # important: do not remove (to avoid errors)
|
|
426
482
|
self.first_plot = True
|
|
427
483
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
428
484
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
429
485
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
430
486
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
487
|
+
self.interp_m_button.config(state=tk.NORMAL)
|
|
488
|
+
if self.cleandata_lacosmic is not None:
|
|
489
|
+
if self.last_dilation is None or self.last_dilation == 0:
|
|
490
|
+
self.interp_l_button.config(state=tk.NORMAL)
|
|
491
|
+
if self.auxdata is not None:
|
|
492
|
+
self.interp_aux_button.config(state=tk.NORMAL)
|
|
493
|
+
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
431
494
|
self.update_display()
|
|
432
495
|
|
|
433
496
|
def exit_review(self):
|
|
434
|
-
|
|
497
|
+
"""Close the review window."""
|
|
498
|
+
self.root.destroy()
|
|
435
499
|
|
|
436
500
|
def on_key(self, event):
|
|
501
|
+
"""Handle key press events."""
|
|
437
502
|
if event.key == 'q':
|
|
438
503
|
pass # Ignore the "q" key to prevent closing the window
|
|
439
504
|
elif event.key == 'r':
|
|
@@ -447,7 +512,16 @@ class ReviewCosmicRay():
|
|
|
447
512
|
self.interp_y()
|
|
448
513
|
elif event.key == 's':
|
|
449
514
|
if self.interp_s_button.cget("state") != "disabled":
|
|
450
|
-
self.
|
|
515
|
+
self.interp_a('surface')
|
|
516
|
+
elif event.key == 'm':
|
|
517
|
+
if self.interp_m_button.cget("state") != "disabled":
|
|
518
|
+
self.interp_a('median')
|
|
519
|
+
elif event.key == 'l':
|
|
520
|
+
if self.interp_l_button.cget("state") != "disabled":
|
|
521
|
+
self.use_lacosmic()
|
|
522
|
+
elif event.key == 'a':
|
|
523
|
+
if self.interp_aux_button.cget("state") != "disabled":
|
|
524
|
+
self.use_auxdata()
|
|
451
525
|
elif event.key == 'right' or event.key == 'c':
|
|
452
526
|
self.continue_cr()
|
|
453
527
|
elif event.key == ',':
|
|
@@ -456,13 +530,14 @@ class ReviewCosmicRay():
|
|
|
456
530
|
self.set_zscale()
|
|
457
531
|
elif event.key == 'e':
|
|
458
532
|
self.exit_review()
|
|
533
|
+
return # important: do not remove (to avoid errors)
|
|
459
534
|
else:
|
|
460
535
|
print(f"Key pressed: {event.key}")
|
|
461
536
|
|
|
462
537
|
def on_click(self, event):
|
|
463
|
-
|
|
538
|
+
"""Handle mouse click events on the image."""
|
|
539
|
+
if event.inaxes == self.ax:
|
|
464
540
|
x, y = event.xdata, event.ydata
|
|
465
|
-
print(f"Clicked at image coordinates: ({x:.2f}, {y:.2f})")
|
|
466
541
|
ix = int(x+0.5) - 1 # from pixel to index
|
|
467
542
|
iy = int(y+0.5) - 1 # from pixel to index
|
|
468
543
|
if int(self.cr_labels[iy, ix]) == self.cr_index:
|
|
@@ -470,17 +545,26 @@ class ReviewCosmicRay():
|
|
|
470
545
|
print(f"Pixel ({ix+1}, {iy+1}) unmarked as cosmic ray.")
|
|
471
546
|
else:
|
|
472
547
|
self.cr_labels[iy, ix] = self.cr_index
|
|
473
|
-
print(f"Pixel ({ix+1}, {iy+1}) marked as cosmic ray.")
|
|
548
|
+
print(f"Pixel ({ix+1}, {iy+1}), with signal {self.data[iy, ix]}, marked as cosmic ray.")
|
|
474
549
|
xcr_list, ycr_list = np.where(self.cr_labels == self.cr_index)
|
|
475
550
|
if len(xcr_list) == 0:
|
|
476
551
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
477
552
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
478
553
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
554
|
+
self.interp_m_button.config(state=tk.DISABLED)
|
|
555
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
556
|
+
self.interp_aux_button.config(state=tk.DISABLED)
|
|
479
557
|
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
480
558
|
else:
|
|
481
559
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
482
560
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
483
561
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
562
|
+
self.interp_m_button.config(state=tk.NORMAL)
|
|
563
|
+
if self.cleandata_lacosmic is not None:
|
|
564
|
+
if self.last_dilation is None or self.last_dilation == 0:
|
|
565
|
+
self.interp_l_button.config(state=tk.NORMAL)
|
|
566
|
+
if self.auxdata is not None:
|
|
567
|
+
self.interp_aux_button.config(state=tk.NORMAL)
|
|
484
568
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
485
569
|
# Update the display to reflect the change
|
|
486
570
|
self.update_display()
|