teareduce 0.5.8__py3-none-any.whl → 0.6.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/__init__.py +8 -1
- teareduce/cleanest/__main__.py +11 -6
- teareduce/cleanest/askextension.py +85 -0
- teareduce/cleanest/cosmicraycleanerapp.py +297 -65
- teareduce/cleanest/definitions.py +55 -9
- teareduce/cleanest/gausskernel2d_elliptical.py +54 -0
- teareduce/cleanest/interpolate.py +65 -48
- teareduce/cleanest/interpolationeditor.py +196 -52
- teareduce/cleanest/lacosmicpad.py +100 -2
- teareduce/cleanest/parametereditor.py +279 -51
- teareduce/cleanest/reviewcosmicray.py +285 -83
- teareduce/cleanest/trackedbutton.py +73 -0
- teareduce/version.py +1 -1
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/METADATA +17 -5
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/RECORD +19 -16
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/WHEEL +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -13,7 +13,14 @@ import tkinter as tk
|
|
|
13
13
|
from tkinter import messagebox
|
|
14
14
|
from tkinter import simpledialog
|
|
15
15
|
|
|
16
|
-
|
|
16
|
+
try:
|
|
17
|
+
from maskfill import maskfill
|
|
18
|
+
except ModuleNotFoundError as e:
|
|
19
|
+
raise ModuleNotFoundError(
|
|
20
|
+
"The 'teareduce.cleanest' module requires the 'ccdproc' and 'maskfill' packages. "
|
|
21
|
+
"Please install teareduce with the 'cleanest' extra dependencies: "
|
|
22
|
+
"`pip install teareduce[cleanest]`."
|
|
23
|
+
) from e
|
|
17
24
|
import matplotlib.pyplot as plt
|
|
18
25
|
from matplotlib.backend_bases import key_press_handler
|
|
19
26
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
@@ -21,15 +28,19 @@ import numpy as np
|
|
|
21
28
|
from rich import print
|
|
22
29
|
|
|
23
30
|
from .centerchildparent import center_on_parent
|
|
31
|
+
from .definitions import DEFAULT_TK_WINDOW_SIZE_X
|
|
32
|
+
from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
|
|
24
33
|
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
25
34
|
from .imagedisplay import ImageDisplay
|
|
26
35
|
from .interpolation_a import interpolation_a
|
|
27
36
|
from .interpolation_x import interpolation_x
|
|
28
37
|
from .interpolation_y import interpolation_y
|
|
38
|
+
from .trackedbutton import TrackedTkButton
|
|
29
39
|
|
|
30
40
|
from ..imshow import imshow
|
|
31
41
|
from ..sliceregion import SliceRegion2D
|
|
32
42
|
from ..zscale import zscale
|
|
43
|
+
from ..version import VERSION
|
|
33
44
|
|
|
34
45
|
import matplotlib
|
|
35
46
|
|
|
@@ -42,6 +53,8 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
42
53
|
def __init__(
|
|
43
54
|
self,
|
|
44
55
|
root,
|
|
56
|
+
root_width,
|
|
57
|
+
root_height,
|
|
45
58
|
data,
|
|
46
59
|
auxdata,
|
|
47
60
|
cleandata_lacosmic,
|
|
@@ -52,6 +65,10 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
52
65
|
last_dilation=None,
|
|
53
66
|
last_npoints=None,
|
|
54
67
|
last_degree=None,
|
|
68
|
+
last_maskfill_size=None,
|
|
69
|
+
last_maskfill_operator=None,
|
|
70
|
+
last_maskfill_smooth=None,
|
|
71
|
+
last_maskfill_verbose=None,
|
|
55
72
|
):
|
|
56
73
|
"""Initialize the review window.
|
|
57
74
|
|
|
@@ -59,6 +76,10 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
59
76
|
----------
|
|
60
77
|
root : tk.Toplevel
|
|
61
78
|
The parent Tkinter root window.
|
|
79
|
+
root_width : int
|
|
80
|
+
The width of the root window. The review window is scaled accordingly.
|
|
81
|
+
root_height : int
|
|
82
|
+
The height of the root window. The review window is scaled accordingly.
|
|
62
83
|
data : 2D numpy array
|
|
63
84
|
The original image data.
|
|
64
85
|
auxdata : 2D numpy array or None
|
|
@@ -83,6 +104,14 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
83
104
|
The last used number of points parameter for interpolation.
|
|
84
105
|
last_degree : int or None, optional
|
|
85
106
|
The last used degree parameter for interpolation.
|
|
107
|
+
last_maskfill_size : int or None, optional
|
|
108
|
+
The last used maskfill size parameter.
|
|
109
|
+
last_maskfill_operator : str or None, optional
|
|
110
|
+
The last used maskfill operator parameter.
|
|
111
|
+
last_maskfill_smooth : bool or None, optional
|
|
112
|
+
The last used maskfill smooth parameter.
|
|
113
|
+
last_maskfill_verbose : bool or None, optional
|
|
114
|
+
The last used maskfill verbose parameter.
|
|
86
115
|
|
|
87
116
|
Methods
|
|
88
117
|
-------
|
|
@@ -119,6 +148,10 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
119
148
|
----------
|
|
120
149
|
root : tk.Toplevel
|
|
121
150
|
The parent Tkinter root window.
|
|
151
|
+
factor_width : float
|
|
152
|
+
The scaling factor for the width of the review window.
|
|
153
|
+
factor_height : float
|
|
154
|
+
The scaling factor for the height of the review window.
|
|
122
155
|
data : 2D numpy array
|
|
123
156
|
The original image data.
|
|
124
157
|
auxdata : 2D numpy array or None
|
|
@@ -139,19 +172,33 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
139
172
|
Degree parameter for interpolation.
|
|
140
173
|
npoints : int
|
|
141
174
|
Number of points parameter for interpolation.
|
|
175
|
+
maskfill_size : int
|
|
176
|
+
The last used maskfill size parameter.
|
|
177
|
+
maskfill_operator : str
|
|
178
|
+
The last used maskfill operator parameter.
|
|
179
|
+
maskfill_smooth : bool
|
|
180
|
+
The last used maskfill smooth parameter.
|
|
181
|
+
maskfill_verbose : bool
|
|
182
|
+
The last used maskfill verbose parameter.
|
|
142
183
|
last_dilation : int or None
|
|
143
184
|
The last used dilation parameter employed after L.A.Cosmic
|
|
144
185
|
detection.
|
|
145
186
|
"""
|
|
146
187
|
self.root = root
|
|
147
|
-
self.root.title("Review Cosmic Rays")
|
|
188
|
+
self.root.title(f"Review Cosmic Rays (TEA version {VERSION})")
|
|
189
|
+
self.factor_width = root_width / DEFAULT_TK_WINDOW_SIZE_X
|
|
190
|
+
self.factor_height = root_height / DEFAULT_TK_WINDOW_SIZE_Y
|
|
148
191
|
self.auxdata = auxdata
|
|
149
192
|
if self.auxdata is not None:
|
|
150
193
|
# self.root.geometry("1000x700+100+100") # This does not work in Fedora
|
|
151
|
-
self.
|
|
194
|
+
window_width = int(1000 * self.factor_width + 0.5)
|
|
195
|
+
window_height = int(700 * self.factor_height + 0.5)
|
|
196
|
+
self.root.minsize(window_width, window_height)
|
|
152
197
|
else:
|
|
153
|
-
# self.root.geometry("
|
|
154
|
-
self.
|
|
198
|
+
# self.root.geometry("900x700+100+100") # This does not work in Fedora
|
|
199
|
+
window_width = int(900 * self.factor_width + 0.5)
|
|
200
|
+
window_height = int(700 * self.factor_height + 0.5)
|
|
201
|
+
self.root.minsize(window_width, window_height)
|
|
155
202
|
self.root.update_idletasks()
|
|
156
203
|
self.root.geometry("+100+100")
|
|
157
204
|
self.data = data
|
|
@@ -164,6 +211,10 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
164
211
|
self.first_plot = True
|
|
165
212
|
self.degree = last_degree if last_degree is not None else 1
|
|
166
213
|
self.npoints = last_npoints if last_npoints is not None else 2
|
|
214
|
+
self.maskfill_size = last_maskfill_size if last_maskfill_size is not None else 3
|
|
215
|
+
self.maskfill_operator = last_maskfill_operator if last_maskfill_operator is not None else "median"
|
|
216
|
+
self.maskfill_smooth = last_maskfill_smooth if last_maskfill_smooth is not None else True
|
|
217
|
+
self.maskfill_verbose = last_maskfill_verbose if last_maskfill_verbose is not None else False
|
|
167
218
|
self.last_dilation = last_dilation
|
|
168
219
|
# Make a copy of the original labels to allow pixel re-marking
|
|
169
220
|
self.cr_labels_original = self.cr_labels.copy()
|
|
@@ -180,29 +231,74 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
180
231
|
|
|
181
232
|
def create_widgets(self):
|
|
182
233
|
"""Create the GUI widgets for the review window."""
|
|
234
|
+
# Define instance of TrackedTkButton, that facilitates to show help information
|
|
235
|
+
# for each button displayed in the current application window.
|
|
236
|
+
tkbutton = TrackedTkButton(self.root)
|
|
237
|
+
|
|
183
238
|
# Row 1 of buttons
|
|
184
239
|
self.button_frame1 = tk.Frame(self.root)
|
|
185
240
|
self.button_frame1.pack(pady=5)
|
|
186
|
-
self.ndeg_label =
|
|
187
|
-
self.button_frame1,
|
|
241
|
+
self.ndeg_label = tkbutton.new(
|
|
242
|
+
self.button_frame1,
|
|
243
|
+
text=f"Npoints={self.npoints}, Degree={self.degree}",
|
|
244
|
+
command=self.set_ndeg,
|
|
245
|
+
help_text="Set the Npoints and Degree parameters for interpolation.",
|
|
246
|
+
alttext="Npoints=?, Degree=?",
|
|
188
247
|
)
|
|
189
248
|
self.ndeg_label.pack(side=tk.LEFT, padx=5)
|
|
190
|
-
self.
|
|
249
|
+
self.maskfill_button = tkbutton.new(
|
|
250
|
+
self.button_frame1,
|
|
251
|
+
text="Maskfill params.",
|
|
252
|
+
command=self.set_maskfill_params,
|
|
253
|
+
help_text="Set the parameters for the maskfill method.",
|
|
254
|
+
)
|
|
255
|
+
self.maskfill_button.pack(side=tk.LEFT, padx=5)
|
|
256
|
+
self.remove_crosses_button = tkbutton.new(
|
|
257
|
+
self.button_frame1,
|
|
258
|
+
text="remove all x's",
|
|
259
|
+
command=self.remove_crosses,
|
|
260
|
+
help_text="Remove all cross marks from the image.",
|
|
261
|
+
)
|
|
191
262
|
self.remove_crosses_button.pack(side=tk.LEFT, padx=5)
|
|
192
|
-
self.restore_cr_button =
|
|
263
|
+
self.restore_cr_button = tkbutton.new(
|
|
264
|
+
self.button_frame1,
|
|
265
|
+
text="restore CR",
|
|
266
|
+
command=self.restore_cr,
|
|
267
|
+
help_text="Restore current cosmic ray pixels to their original values.",
|
|
268
|
+
)
|
|
193
269
|
self.restore_cr_button.pack(side=tk.LEFT, padx=5)
|
|
194
270
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
195
|
-
self.next_button =
|
|
271
|
+
self.next_button = tkbutton.new(
|
|
272
|
+
self.button_frame1,
|
|
273
|
+
text="[c]ontinue",
|
|
274
|
+
command=self.continue_cr,
|
|
275
|
+
help_text="Continue to the next cosmic ray.",
|
|
276
|
+
)
|
|
196
277
|
self.next_button.pack(side=tk.LEFT, padx=5)
|
|
197
|
-
self.exit_button =
|
|
278
|
+
self.exit_button = tkbutton.new(
|
|
279
|
+
self.button_frame1,
|
|
280
|
+
text="[e]xit review",
|
|
281
|
+
command=self.exit_review,
|
|
282
|
+
help_text="Exit the cosmic ray review process.",
|
|
283
|
+
)
|
|
198
284
|
self.exit_button.pack(side=tk.LEFT, padx=5)
|
|
199
285
|
|
|
200
286
|
# Row 2 of buttons
|
|
201
287
|
self.button_frame2 = tk.Frame(self.root)
|
|
202
288
|
self.button_frame2.pack(pady=5)
|
|
203
|
-
self.interp_x_button =
|
|
289
|
+
self.interp_x_button = tkbutton.new(
|
|
290
|
+
self.button_frame2,
|
|
291
|
+
text="[x] interp.",
|
|
292
|
+
command=self.interp_x,
|
|
293
|
+
help_text="Perform X-interpolation for the current cosmic ray.",
|
|
294
|
+
)
|
|
204
295
|
self.interp_x_button.pack(side=tk.LEFT, padx=5)
|
|
205
|
-
self.interp_y_button =
|
|
296
|
+
self.interp_y_button = tkbutton.new(
|
|
297
|
+
self.button_frame2,
|
|
298
|
+
text="[y] interp.",
|
|
299
|
+
command=self.interp_y,
|
|
300
|
+
help_text="Perform Y-interpolation for the current cosmic ray.",
|
|
301
|
+
)
|
|
206
302
|
self.interp_y_button.pack(side=tk.LEFT, padx=5)
|
|
207
303
|
# it is important to use lambda here to pass the method argument correctly
|
|
208
304
|
# (avoiding the execution of the function at button creation time, which would happen
|
|
@@ -211,23 +307,51 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
211
307
|
# the function is trying to deactivate the buttons before they are created, which
|
|
212
308
|
# would lead to an error; in addition, since I have two buttons calling the same function
|
|
213
309
|
# with different arguments, using lambda allows to differentiate them)
|
|
214
|
-
self.interp_s_button =
|
|
215
|
-
self.button_frame2,
|
|
310
|
+
self.interp_s_button = tkbutton.new(
|
|
311
|
+
self.button_frame2,
|
|
312
|
+
text="[s]urface interp.",
|
|
313
|
+
command=lambda: self.interp_a("surface"),
|
|
314
|
+
help_text="Perform surface interpolation for the current cosmic ray.",
|
|
216
315
|
)
|
|
217
316
|
self.interp_s_button.pack(side=tk.LEFT, padx=5)
|
|
218
|
-
self.interp_d_button =
|
|
317
|
+
self.interp_d_button = tkbutton.new(
|
|
318
|
+
self.button_frame2,
|
|
319
|
+
text="me[d]ian",
|
|
320
|
+
command=lambda: self.interp_a("median"),
|
|
321
|
+
help_text="Perform median interpolation for the current cosmic ray.",
|
|
322
|
+
)
|
|
219
323
|
self.interp_d_button.pack(side=tk.LEFT, padx=5)
|
|
220
|
-
self.interp_m_button =
|
|
324
|
+
self.interp_m_button = tkbutton.new(
|
|
325
|
+
self.button_frame2,
|
|
326
|
+
text="[m]ean",
|
|
327
|
+
command=lambda: self.interp_a("mean"),
|
|
328
|
+
help_text="Perform mean interpolation for the current cosmic ray.",
|
|
329
|
+
)
|
|
221
330
|
self.interp_m_button.pack(side=tk.LEFT, padx=5)
|
|
222
|
-
self.interp_l_button =
|
|
331
|
+
self.interp_l_button = tkbutton.new(
|
|
332
|
+
self.button_frame2,
|
|
333
|
+
text="[l]acosmic",
|
|
334
|
+
command=self.use_lacosmic,
|
|
335
|
+
help_text="Use L.A.Cosmic interpolation for the current cosmic ray.",
|
|
336
|
+
)
|
|
223
337
|
self.interp_l_button.pack(side=tk.LEFT, padx=5)
|
|
224
338
|
if self.last_dilation is not None and self.last_dilation > 0:
|
|
225
339
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
226
340
|
if self.cleandata_lacosmic is None:
|
|
227
341
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
228
|
-
self.interp_maskfill_button =
|
|
342
|
+
self.interp_maskfill_button = tkbutton.new(
|
|
343
|
+
self.button_frame2,
|
|
344
|
+
text="mask[f]ill",
|
|
345
|
+
command=self.use_maskfill,
|
|
346
|
+
help_text="Perform maskfill interpolation for the current cosmic ray.",
|
|
347
|
+
)
|
|
229
348
|
self.interp_maskfill_button.pack(side=tk.LEFT, padx=5)
|
|
230
|
-
self.interp_aux_button =
|
|
349
|
+
self.interp_aux_button = tkbutton.new(
|
|
350
|
+
self.button_frame2,
|
|
351
|
+
text="[a]ux. data",
|
|
352
|
+
command=self.use_auxdata,
|
|
353
|
+
help_text="Use auxiliary data for interpolation of the current cosmic ray.",
|
|
354
|
+
)
|
|
231
355
|
self.interp_aux_button.pack(side=tk.LEFT, padx=5)
|
|
232
356
|
if self.auxdata is None:
|
|
233
357
|
self.interp_aux_button.config(state=tk.DISABLED)
|
|
@@ -236,20 +360,53 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
236
360
|
self.button_frame3 = tk.Frame(self.root)
|
|
237
361
|
self.button_frame3.pack(pady=5)
|
|
238
362
|
vmin, vmax = zscale(self.data)
|
|
239
|
-
self.vmin_button =
|
|
363
|
+
self.vmin_button = tkbutton.new(
|
|
364
|
+
self.button_frame3,
|
|
365
|
+
text=f"vmin: {vmin:.2f}",
|
|
366
|
+
command=self.set_vmin,
|
|
367
|
+
help_text="Set the minimum value for the display scale.",
|
|
368
|
+
alttext="vmin: ??",
|
|
369
|
+
)
|
|
240
370
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
241
|
-
self.vmax_button =
|
|
371
|
+
self.vmax_button = tkbutton.new(
|
|
372
|
+
self.button_frame3,
|
|
373
|
+
text=f"vmax: {vmax:.2f}",
|
|
374
|
+
command=self.set_vmax,
|
|
375
|
+
help_text="Set the maximum value for the display scale.",
|
|
376
|
+
alttext="vmax: ??",
|
|
377
|
+
)
|
|
242
378
|
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
243
|
-
self.set_minmax_button =
|
|
379
|
+
self.set_minmax_button = tkbutton.new(
|
|
380
|
+
self.button_frame3,
|
|
381
|
+
text="minmax [,]",
|
|
382
|
+
command=self.set_minmax,
|
|
383
|
+
help_text="Set the display scale to the minimum and maximum data values.",
|
|
384
|
+
)
|
|
244
385
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
245
|
-
self.set_zscale_button =
|
|
386
|
+
self.set_zscale_button = tkbutton.new(
|
|
387
|
+
self.button_frame3,
|
|
388
|
+
text="zscale [/]",
|
|
389
|
+
command=self.set_zscale,
|
|
390
|
+
help_text="Set the display scale using zscale.",
|
|
391
|
+
)
|
|
246
392
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
393
|
+
self.help_button = tkbutton.new(
|
|
394
|
+
self.button_frame3,
|
|
395
|
+
text="Help",
|
|
396
|
+
command=tkbutton.show_help,
|
|
397
|
+
help_text="Show help information for all buttons.",
|
|
398
|
+
)
|
|
399
|
+
self.help_button.pack(side=tk.LEFT, padx=5)
|
|
247
400
|
|
|
248
401
|
# Figure
|
|
249
402
|
if self.auxdata is not None:
|
|
250
|
-
self.fig, (self.
|
|
403
|
+
self.fig, (self.ax, self.ax_aux) = plt.subplots(
|
|
404
|
+
ncols=2, figsize=(11 * self.factor_width, 5.5 * self.factor_height), constrained_layout=True
|
|
405
|
+
)
|
|
251
406
|
else:
|
|
252
|
-
self.fig, self.ax = plt.subplots(
|
|
407
|
+
self.fig, self.ax = plt.subplots(
|
|
408
|
+
figsize=(9 * self.factor_width, 5.5 * self.factor_height), constrained_layout=True
|
|
409
|
+
)
|
|
253
410
|
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
|
|
254
411
|
self.canvas.get_tk_widget().pack(padx=5, pady=5)
|
|
255
412
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
@@ -369,18 +526,66 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
369
526
|
self.npoints = new_npoints
|
|
370
527
|
self.ndeg_label.config(text=f"Npoints={self.npoints}, Degree={self.degree}")
|
|
371
528
|
|
|
529
|
+
def set_maskfill_params(self):
|
|
530
|
+
"""Set the maskfill parameters."""
|
|
531
|
+
new_size = simpledialog.askinteger(
|
|
532
|
+
"Set Maskfill Size", "Enter Maskfill Size (odd integer >=1):", initialvalue=self.maskfill_size, minvalue=1
|
|
533
|
+
)
|
|
534
|
+
if new_size is None:
|
|
535
|
+
return
|
|
536
|
+
if new_size % 2 == 0:
|
|
537
|
+
messagebox.showerror("Input Error", "Maskfill size must be an odd integer.")
|
|
538
|
+
return
|
|
539
|
+
new_operator = simpledialog.askstring(
|
|
540
|
+
"Set Maskfill Operator",
|
|
541
|
+
"Enter Maskfill Operator ('median' or 'mean'):",
|
|
542
|
+
initialvalue=self.maskfill_operator,
|
|
543
|
+
)
|
|
544
|
+
if new_operator is None:
|
|
545
|
+
return
|
|
546
|
+
if new_operator not in ["median", "mean"]:
|
|
547
|
+
messagebox.showerror("Input Error", "Maskfill operator must be 'median' or 'mean'.")
|
|
548
|
+
return
|
|
549
|
+
smooth_str = simpledialog.askstring(
|
|
550
|
+
"Set Maskfill Smooth",
|
|
551
|
+
"Enter Maskfill Smooth ('True' or 'False'):",
|
|
552
|
+
initialvalue=str(self.maskfill_smooth),
|
|
553
|
+
)
|
|
554
|
+
if smooth_str is None:
|
|
555
|
+
return
|
|
556
|
+
smooth_str = smooth_str.strip().lower()
|
|
557
|
+
if smooth_str == "true":
|
|
558
|
+
new_smooth = True
|
|
559
|
+
elif smooth_str == "false":
|
|
560
|
+
new_smooth = False
|
|
561
|
+
else:
|
|
562
|
+
messagebox.showerror("Input Error", "Maskfill Smooth must be 'True' or 'False'.")
|
|
563
|
+
return
|
|
564
|
+
verbose_str = simpledialog.askstring(
|
|
565
|
+
"Set Maskfill Verbose",
|
|
566
|
+
"Enter Maskfill Verbose ('True' or 'False'):",
|
|
567
|
+
initialvalue=str(self.maskfill_verbose),
|
|
568
|
+
)
|
|
569
|
+
if verbose_str is None:
|
|
570
|
+
return
|
|
571
|
+
verbose_str = verbose_str.strip().lower()
|
|
572
|
+
if verbose_str == "true":
|
|
573
|
+
new_verbose = True
|
|
574
|
+
elif verbose_str == "false":
|
|
575
|
+
new_verbose = False
|
|
576
|
+
else:
|
|
577
|
+
messagebox.showerror("Input Error", "Maskfill Verbose must be 'True' or 'False'.")
|
|
578
|
+
return
|
|
579
|
+
self.maskfill_size = new_size
|
|
580
|
+
self.maskfill_operator = new_operator
|
|
581
|
+
self.maskfill_smooth = new_smooth
|
|
582
|
+
self.maskfill_verbose = new_verbose
|
|
583
|
+
|
|
372
584
|
def set_buttons_after_cleaning_cr(self):
|
|
373
585
|
"""Set the state of buttons after cleaning a cosmic ray."""
|
|
586
|
+
self.disable_interpolation_buttons()
|
|
374
587
|
self.restore_cr_button.config(state=tk.NORMAL)
|
|
375
588
|
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
376
|
-
self.interp_x_button.config(state=tk.DISABLED)
|
|
377
|
-
self.interp_y_button.config(state=tk.DISABLED)
|
|
378
|
-
self.interp_s_button.config(state=tk.DISABLED)
|
|
379
|
-
self.interp_d_button.config(state=tk.DISABLED)
|
|
380
|
-
self.interp_m_button.config(state=tk.DISABLED)
|
|
381
|
-
self.interp_l_button.config(state=tk.DISABLED)
|
|
382
|
-
self.interp_maskfill_button.config(state=tk.DISABLED)
|
|
383
|
-
self.interp_aux_button.config(state=tk.DISABLED)
|
|
384
589
|
|
|
385
590
|
def interp_x(self):
|
|
386
591
|
"""Perform x-direction interpolation to clean a cosmic ray."""
|
|
@@ -388,6 +593,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
388
593
|
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x interpolation.")
|
|
389
594
|
return
|
|
390
595
|
print(f"X-interpolation of cosmic ray {self.cr_index}")
|
|
596
|
+
print(f"Interpolation parameters: Npoints={self.npoints}, Degree={self.degree}")
|
|
391
597
|
interpolation_performed, xfit_all, yfit_all = interpolation_x(
|
|
392
598
|
data=self.data,
|
|
393
599
|
mask_fixed=self.mask_fixed,
|
|
@@ -410,6 +616,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
410
616
|
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for y interpolation.")
|
|
411
617
|
return
|
|
412
618
|
print(f"Y-interpolation of cosmic ray {self.cr_index}")
|
|
619
|
+
print(f"Interpolation parameters: Npoints={self.npoints}, Degree={self.degree}")
|
|
413
620
|
interpolation_performed, xfit_all, yfit_all = interpolation_y(
|
|
414
621
|
data=self.data,
|
|
415
622
|
mask_fixed=self.mask_fixed,
|
|
@@ -435,6 +642,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
435
642
|
The interpolation method to use ('surface', 'median' or 'mean').
|
|
436
643
|
"""
|
|
437
644
|
print(f"{method} interpolation of cosmic ray {self.cr_index}")
|
|
645
|
+
print(f'Interpolation parameters: Npoints={self.npoints}, method="{method}"')
|
|
438
646
|
interpolation_performed, xfit_all, yfit_all = interpolation_a(
|
|
439
647
|
data=self.data,
|
|
440
648
|
mask_fixed=self.mask_fixed,
|
|
@@ -468,6 +676,12 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
468
676
|
def use_maskfill(self):
|
|
469
677
|
"""Use maskfill cleaned data to clean a cosmic ray."""
|
|
470
678
|
print(f"Maskfill interpolation of cosmic ray {self.cr_index}")
|
|
679
|
+
print(
|
|
680
|
+
f"Maskfill parameters: size={self.maskfill_size}, "
|
|
681
|
+
f"operator={self.maskfill_operator}, "
|
|
682
|
+
f"smooth={self.maskfill_smooth}, "
|
|
683
|
+
f"verbose={self.maskfill_verbose}"
|
|
684
|
+
)
|
|
471
685
|
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
472
686
|
mask = np.zeros(self.data.shape, dtype=bool)
|
|
473
687
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
@@ -475,9 +689,10 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
475
689
|
smoothed_output, _ = maskfill(
|
|
476
690
|
input_image=self.data,
|
|
477
691
|
mask=mask,
|
|
478
|
-
size=
|
|
479
|
-
operator=
|
|
480
|
-
smooth=
|
|
692
|
+
size=self.maskfill_size,
|
|
693
|
+
operator=self.maskfill_operator,
|
|
694
|
+
smooth=self.maskfill_smooth,
|
|
695
|
+
verbose=self.maskfill_verbose,
|
|
481
696
|
)
|
|
482
697
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
483
698
|
self.data[iy, ix] = smoothed_output[iy, ix]
|
|
@@ -506,35 +721,21 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
506
721
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
507
722
|
self.cr_labels[iy, ix] = 0
|
|
508
723
|
print(f"Removed all pixels of cosmic ray {self.cr_index}")
|
|
509
|
-
self.
|
|
510
|
-
self.interp_x_button.config(state=tk.DISABLED)
|
|
511
|
-
self.interp_y_button.config(state=tk.DISABLED)
|
|
512
|
-
self.interp_s_button.config(state=tk.DISABLED)
|
|
513
|
-
self.interp_d_button.config(state=tk.DISABLED)
|
|
514
|
-
self.interp_m_button.config(state=tk.DISABLED)
|
|
515
|
-
self.interp_l_button.config(state=tk.DISABLED)
|
|
516
|
-
self.interp_aux_button.config(state=tk.DISABLED)
|
|
724
|
+
self.disable_interpolation_buttons()
|
|
517
725
|
self.update_display()
|
|
518
726
|
|
|
519
727
|
def restore_cr(self):
|
|
520
728
|
"""Restore all pixels of the current cosmic ray to their original values."""
|
|
521
729
|
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
730
|
+
if len(xcr_list) == 0:
|
|
731
|
+
print(f"No pixels to restore for cosmic ray {self.cr_index}")
|
|
732
|
+
return
|
|
522
733
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
523
734
|
self.data[iy, ix] = self.data_original[iy, ix]
|
|
524
735
|
self.mask_fixed[iy, ix] = False
|
|
525
|
-
self.interp_x_button.config(state=tk.NORMAL)
|
|
526
|
-
self.interp_y_button.config(state=tk.NORMAL)
|
|
527
|
-
self.interp_s_button.config(state=tk.NORMAL)
|
|
528
|
-
self.interp_d_button.config(state=tk.NORMAL)
|
|
529
|
-
self.interp_m_button.config(state=tk.NORMAL)
|
|
530
|
-
if self.cleandata_lacosmic is not None:
|
|
531
|
-
if self.last_dilation is None or self.last_dilation == 0:
|
|
532
|
-
self.interp_l_button.config(state=tk.NORMAL)
|
|
533
|
-
self.interp_maskfill_button.config(state=tk.NORMAL)
|
|
534
|
-
if self.auxdata is not None:
|
|
535
|
-
self.interp_aux_button.config(state=tk.NORMAL)
|
|
536
736
|
print(f"Restored all pixels of cosmic ray {self.cr_index}")
|
|
537
737
|
self.num_cr_cleaned -= 1
|
|
738
|
+
self.enable_interpolation_buttons()
|
|
538
739
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
539
740
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
540
741
|
self.update_display()
|
|
@@ -550,16 +751,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
550
751
|
return # important: do not remove (to avoid errors)
|
|
551
752
|
self.first_plot = True
|
|
552
753
|
self.restore_cr_button.config(state=tk.DISABLED)
|
|
553
|
-
self.
|
|
554
|
-
self.interp_y_button.config(state=tk.NORMAL)
|
|
555
|
-
self.interp_s_button.config(state=tk.NORMAL)
|
|
556
|
-
self.interp_d_button.config(state=tk.NORMAL)
|
|
557
|
-
self.interp_m_button.config(state=tk.NORMAL)
|
|
558
|
-
if self.cleandata_lacosmic is not None:
|
|
559
|
-
if self.last_dilation is None or self.last_dilation == 0:
|
|
560
|
-
self.interp_l_button.config(state=tk.NORMAL)
|
|
561
|
-
if self.auxdata is not None:
|
|
562
|
-
self.interp_aux_button.config(state=tk.NORMAL)
|
|
754
|
+
self.enable_interpolation_buttons()
|
|
563
755
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
564
756
|
self.update_display()
|
|
565
757
|
|
|
@@ -624,25 +816,35 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
624
816
|
print(f"Pixel ({ix+1}, {iy+1}), with signal {self.data[iy, ix]}, marked as cosmic ray.")
|
|
625
817
|
xcr_list, ycr_list = np.where(self.cr_labels == self.cr_index)
|
|
626
818
|
if len(xcr_list) == 0:
|
|
627
|
-
self.
|
|
628
|
-
self.interp_y_button.config(state=tk.DISABLED)
|
|
629
|
-
self.interp_s_button.config(state=tk.DISABLED)
|
|
630
|
-
self.interp_d_button.config(state=tk.DISABLED)
|
|
631
|
-
self.interp_m_button.config(state=tk.DISABLED)
|
|
632
|
-
self.interp_l_button.config(state=tk.DISABLED)
|
|
633
|
-
self.interp_aux_button.config(state=tk.DISABLED)
|
|
819
|
+
self.disable_interpolation_buttons()
|
|
634
820
|
self.remove_crosses_button.config(state=tk.DISABLED)
|
|
635
821
|
else:
|
|
636
|
-
self.
|
|
637
|
-
self.interp_y_button.config(state=tk.NORMAL)
|
|
638
|
-
self.interp_s_button.config(state=tk.NORMAL)
|
|
639
|
-
self.interp_d_button.config(state=tk.NORMAL)
|
|
640
|
-
self.interp_m_button.config(state=tk.NORMAL)
|
|
641
|
-
if self.cleandata_lacosmic is not None:
|
|
642
|
-
if self.last_dilation is None or self.last_dilation == 0:
|
|
643
|
-
self.interp_l_button.config(state=tk.NORMAL)
|
|
644
|
-
if self.auxdata is not None:
|
|
645
|
-
self.interp_aux_button.config(state=tk.NORMAL)
|
|
822
|
+
self.enable_interpolation_buttons()
|
|
646
823
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
647
824
|
# Update the display to reflect the change
|
|
648
825
|
self.update_display()
|
|
826
|
+
|
|
827
|
+
def disable_interpolation_buttons(self):
|
|
828
|
+
"""Disable all interpolation buttons."""
|
|
829
|
+
self.interp_x_button.config(state=tk.DISABLED)
|
|
830
|
+
self.interp_y_button.config(state=tk.DISABLED)
|
|
831
|
+
self.interp_s_button.config(state=tk.DISABLED)
|
|
832
|
+
self.interp_d_button.config(state=tk.DISABLED)
|
|
833
|
+
self.interp_m_button.config(state=tk.DISABLED)
|
|
834
|
+
self.interp_l_button.config(state=tk.DISABLED)
|
|
835
|
+
self.interp_maskfill_button.config(state=tk.DISABLED)
|
|
836
|
+
self.interp_aux_button.config(state=tk.DISABLED)
|
|
837
|
+
|
|
838
|
+
def enable_interpolation_buttons(self):
|
|
839
|
+
"""Enable all interpolation buttons."""
|
|
840
|
+
self.interp_x_button.config(state=tk.NORMAL)
|
|
841
|
+
self.interp_y_button.config(state=tk.NORMAL)
|
|
842
|
+
self.interp_s_button.config(state=tk.NORMAL)
|
|
843
|
+
self.interp_d_button.config(state=tk.NORMAL)
|
|
844
|
+
self.interp_m_button.config(state=tk.NORMAL)
|
|
845
|
+
if self.cleandata_lacosmic is not None:
|
|
846
|
+
if self.last_dilation is None or self.last_dilation == 0:
|
|
847
|
+
self.interp_l_button.config(state=tk.NORMAL)
|
|
848
|
+
self.interp_maskfill_button.config(state=tk.NORMAL)
|
|
849
|
+
if self.auxdata is not None:
|
|
850
|
+
self.interp_aux_button.config(state=tk.NORMAL)
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0+
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Tracked button widget for the cleanest module."""
|
|
11
|
+
|
|
12
|
+
import tkinter as tk
|
|
13
|
+
from rich.console import Console
|
|
14
|
+
|
|
15
|
+
console = Console()
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
class TrackedTkButton:
|
|
19
|
+
"""Class to create tracked Tkinter buttons with help text."""
|
|
20
|
+
|
|
21
|
+
def __init__(self, root):
|
|
22
|
+
"""Initialize the TrackedTkButton instance.
|
|
23
|
+
|
|
24
|
+
Initializes an empty list to store button information.
|
|
25
|
+
|
|
26
|
+
Parameters
|
|
27
|
+
----------
|
|
28
|
+
root : tk.Tk or tk.Frame
|
|
29
|
+
The parent widget where the help information about
|
|
30
|
+
the actions associated to each button will be displayed.
|
|
31
|
+
"""
|
|
32
|
+
self.root = root
|
|
33
|
+
self.buttons_info = []
|
|
34
|
+
|
|
35
|
+
def new(self, parent, text, command, help_text, alttext=None, **kwargs):
|
|
36
|
+
"""Create a Tkinter button with tracking information.
|
|
37
|
+
|
|
38
|
+
Parameters
|
|
39
|
+
----------
|
|
40
|
+
parent : tk.Widget
|
|
41
|
+
The parent widget where the button will be placed.
|
|
42
|
+
text : str
|
|
43
|
+
The text to display on the button.
|
|
44
|
+
command : callable
|
|
45
|
+
The function to call when the button is pressed.
|
|
46
|
+
help_text : str
|
|
47
|
+
The help text associated with the button.
|
|
48
|
+
alttext : str, optional
|
|
49
|
+
Alternative text for the button.
|
|
50
|
+
**kwargs : dict
|
|
51
|
+
Additional keyword arguments to pass to the Tkinter Button constructor.
|
|
52
|
+
|
|
53
|
+
Returns
|
|
54
|
+
-------
|
|
55
|
+
button : tk.Button
|
|
56
|
+
The created Tkinter button.
|
|
57
|
+
"""
|
|
58
|
+
button = tk.Button(parent, text=text, command=command, **kwargs)
|
|
59
|
+
self.buttons_info.append({"button": button, "text": text, "help_text": help_text, "alttext": alttext})
|
|
60
|
+
return button
|
|
61
|
+
|
|
62
|
+
def show_help(self):
|
|
63
|
+
"""Display help information for all tracked buttons."""
|
|
64
|
+
console.rule("[bold red]Button Help Information[/bold red]")
|
|
65
|
+
for info in self.buttons_info:
|
|
66
|
+
if info["alttext"] is not None:
|
|
67
|
+
text = info["alttext"]
|
|
68
|
+
else:
|
|
69
|
+
text = info["text"]
|
|
70
|
+
# replace '[' by '\[' to avoid formatting issues
|
|
71
|
+
text = text.replace("[", "\\[")
|
|
72
|
+
console.print(f"[bold blue]{text}[/bold blue]: {info['help_text']}")
|
|
73
|
+
console.rule()
|