teareduce 0.5.9__py3-none-any.whl → 0.6.1__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 +2 -2
- teareduce/cleanest/__main__.py +11 -5
- teareduce/cleanest/askextension.py +85 -0
- teareduce/cleanest/cosmicraycleanerapp.py +294 -71
- teareduce/cleanest/definitions.py +55 -9
- teareduce/cleanest/gausskernel2d_elliptical.py +54 -0
- teareduce/cleanest/imagedisplay.py +1 -0
- teareduce/cleanest/interpolate.py +79 -48
- teareduce/cleanest/interpolation_a.py +1 -0
- teareduce/cleanest/interpolation_x.py +1 -0
- teareduce/cleanest/interpolation_y.py +1 -0
- teareduce/cleanest/interpolationeditor.py +199 -53
- teareduce/cleanest/lacosmicpad.py +95 -2
- teareduce/cleanest/mergemasks.py +11 -1
- teareduce/cleanest/parametereditor.py +279 -51
- teareduce/cleanest/reviewcosmicray.py +245 -30
- teareduce/cleanest/trackedbutton.py +73 -0
- teareduce/version.py +1 -1
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/METADATA +2 -2
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/RECORD +24 -21
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/WHEEL +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.1.dist-info}/top_level.txt +0 -0
|
@@ -13,14 +13,34 @@ import tkinter as tk
|
|
|
13
13
|
from tkinter import messagebox
|
|
14
14
|
from tkinter import ttk
|
|
15
15
|
|
|
16
|
+
from rich import print
|
|
17
|
+
|
|
16
18
|
from .centerchildparent import center_on_parent
|
|
17
19
|
from .definitions import VALID_CLEANING_METHODS
|
|
20
|
+
from .definitions import MASKFILL_OPERATOR_VALUES
|
|
18
21
|
|
|
19
22
|
|
|
20
23
|
class InterpolationEditor:
|
|
21
24
|
"""Dialog to select interpolation cleaning parameters."""
|
|
22
25
|
|
|
23
|
-
def __init__(
|
|
26
|
+
def __init__(
|
|
27
|
+
self,
|
|
28
|
+
root,
|
|
29
|
+
last_dilation,
|
|
30
|
+
last_npoints,
|
|
31
|
+
last_degree,
|
|
32
|
+
last_maskfill_size,
|
|
33
|
+
last_maskfill_operator,
|
|
34
|
+
last_maskfill_smooth,
|
|
35
|
+
last_maskfill_verbose,
|
|
36
|
+
auxdata,
|
|
37
|
+
cleandata_lacosmic,
|
|
38
|
+
xmin,
|
|
39
|
+
xmax,
|
|
40
|
+
ymin,
|
|
41
|
+
ymax,
|
|
42
|
+
imgshape,
|
|
43
|
+
):
|
|
24
44
|
"""Initialize the interpolation editor dialog.
|
|
25
45
|
|
|
26
46
|
Parameters
|
|
@@ -33,8 +53,18 @@ class InterpolationEditor:
|
|
|
33
53
|
The last used number of points for interpolation.
|
|
34
54
|
last_degree : int
|
|
35
55
|
The last used degree for interpolation.
|
|
56
|
+
last_maskfill_size : int
|
|
57
|
+
The last used maskfill size parameter.
|
|
58
|
+
last_maskfill_operator : str
|
|
59
|
+
The last used maskfill operator parameter.
|
|
60
|
+
last_maskfill_smooth : bool
|
|
61
|
+
The last used maskfill smooth parameter.
|
|
62
|
+
last_maskfill_verbose : bool
|
|
63
|
+
The last used maskfill verbose parameter.
|
|
36
64
|
auxdata : array-like or None
|
|
37
65
|
Auxiliary data for cleaning, if available.
|
|
66
|
+
cleandata_lacosmic : array-like or None
|
|
67
|
+
Cleaned data from L.A.Cosmic, if available.
|
|
38
68
|
xmin : float
|
|
39
69
|
Minimum x value of the data. From 1 to NAXIS1.
|
|
40
70
|
xmax : float
|
|
@@ -56,8 +86,6 @@ class InterpolationEditor:
|
|
|
56
86
|
Handle the Cancel button click event.
|
|
57
87
|
action_on_method_change()
|
|
58
88
|
Handle changes in the selected cleaning method.
|
|
59
|
-
check_interp_methods()
|
|
60
|
-
Check that all interpolation methods are valid.
|
|
61
89
|
|
|
62
90
|
Attributes
|
|
63
91
|
----------
|
|
@@ -67,6 +95,8 @@ class InterpolationEditor:
|
|
|
67
95
|
The last used dilation parameter.
|
|
68
96
|
auxdata : array-like or None
|
|
69
97
|
Auxiliary data for cleaning, if available.
|
|
98
|
+
cleandata_lacosmic : array-like or None
|
|
99
|
+
Cleaned data from L.A.Cosmic, if available.
|
|
70
100
|
dict_interp_methods : dict
|
|
71
101
|
Mapping of interpolation method names to their codes.
|
|
72
102
|
cleaning_method : str or None
|
|
@@ -75,6 +105,12 @@ class InterpolationEditor:
|
|
|
75
105
|
The number of points for interpolation.
|
|
76
106
|
degree : int
|
|
77
107
|
The degree for interpolation.
|
|
108
|
+
maskfill_size : int
|
|
109
|
+
The size parameter for maskfill.
|
|
110
|
+
maskfill_operator : str
|
|
111
|
+
The operator parameter for maskfill.
|
|
112
|
+
maskfill_smooth : bool
|
|
113
|
+
The smooth parameter for maskfill.
|
|
78
114
|
xmin : float
|
|
79
115
|
Minimum x value of the data. From 1 to NAXIS1.
|
|
80
116
|
xmax : float
|
|
@@ -90,21 +126,15 @@ class InterpolationEditor:
|
|
|
90
126
|
self.root.title("Cleaning Parameters")
|
|
91
127
|
self.last_dilation = last_dilation
|
|
92
128
|
self.auxdata = auxdata
|
|
93
|
-
self.
|
|
94
|
-
"x interp.": "x",
|
|
95
|
-
"y interp.": "y",
|
|
96
|
-
"surface interp.": "a-plane",
|
|
97
|
-
"median": "a-median",
|
|
98
|
-
"mean": "a-mean",
|
|
99
|
-
"lacosmic": "lacosmic",
|
|
100
|
-
"maskfill": "maskfill",
|
|
101
|
-
"auxdata": "auxdata",
|
|
102
|
-
}
|
|
103
|
-
self.check_interp_methods()
|
|
129
|
+
self.cleandata_lacosmic = cleandata_lacosmic
|
|
104
130
|
# Initialize parameters
|
|
105
131
|
self.cleaning_method = None
|
|
106
132
|
self.npoints = last_npoints
|
|
107
133
|
self.degree = last_degree
|
|
134
|
+
self.maskfill_size = last_maskfill_size
|
|
135
|
+
self.maskfill_operator = last_maskfill_operator
|
|
136
|
+
self.maskfill_smooth = last_maskfill_smooth
|
|
137
|
+
self.maskfill_verbose = last_maskfill_verbose
|
|
108
138
|
self.xmin = xmin
|
|
109
139
|
self.xmax = xmax
|
|
110
140
|
self.ymin = ymin
|
|
@@ -129,38 +159,48 @@ class InterpolationEditor:
|
|
|
129
159
|
bold_font = default_font.copy()
|
|
130
160
|
bold_font.configure(weight="bold", size=default_font.cget("size") + 2)
|
|
131
161
|
subtitle_label = tk.Label(main_frame, text="Select Cleaning Method", font=bold_font)
|
|
132
|
-
subtitle_label.grid(row=row, column=0, columnspan=
|
|
162
|
+
subtitle_label.grid(row=row, column=0, columnspan=7, pady=(0, 15))
|
|
133
163
|
row += 1
|
|
134
164
|
|
|
135
165
|
# Create labels and entry fields for each cleaning method
|
|
136
166
|
row = 1
|
|
167
|
+
column = 0
|
|
137
168
|
self.cleaning_method_var = tk.StringVar(value="surface interp.")
|
|
138
|
-
for interp_method in
|
|
139
|
-
|
|
169
|
+
for interp_method in VALID_CLEANING_METHODS.keys():
|
|
170
|
+
state = "normal"
|
|
140
171
|
# Skip replace by L.A.Cosmic values if last dilation is not zero
|
|
141
|
-
if interp_method == "lacosmic"
|
|
142
|
-
|
|
172
|
+
if interp_method == "lacosmic":
|
|
173
|
+
if self.last_dilation != 0:
|
|
174
|
+
state = "disabled"
|
|
175
|
+
if self.cleandata_lacosmic is None:
|
|
176
|
+
state = "disabled"
|
|
143
177
|
# Skip auxdata method if auxdata is not available
|
|
144
178
|
if interp_method == "auxdata" and self.auxdata is None:
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
179
|
+
state = "disabled"
|
|
180
|
+
tk.Radiobutton(
|
|
181
|
+
main_frame,
|
|
182
|
+
text=interp_method,
|
|
183
|
+
variable=self.cleaning_method_var,
|
|
184
|
+
value=interp_method,
|
|
185
|
+
command=self.action_on_method_change,
|
|
186
|
+
state=state,
|
|
187
|
+
).grid(row=row, column=column, sticky="w", padx=5, pady=5)
|
|
188
|
+
column += 1
|
|
189
|
+
if column > 6:
|
|
190
|
+
column = 0
|
|
154
191
|
row += 1
|
|
192
|
+
row += 1
|
|
155
193
|
|
|
156
194
|
# Separator
|
|
157
195
|
separator1 = ttk.Separator(main_frame, orient="horizontal")
|
|
158
|
-
separator1.grid(row=row, column=0, columnspan=
|
|
196
|
+
separator1.grid(row=row, column=0, columnspan=7, sticky="ew", pady=(10, 10))
|
|
159
197
|
row += 1
|
|
160
198
|
|
|
161
199
|
# Subtitle for additional parameters
|
|
162
|
-
|
|
163
|
-
|
|
200
|
+
subtitle_label1 = tk.Label(main_frame, text="Fitting Parameters", font=bold_font)
|
|
201
|
+
subtitle_label1.grid(row=row, column=0, columnspan=3, pady=(0, 15))
|
|
202
|
+
subtitle_label2 = tk.Label(main_frame, text="Maskfill Parameters", font=bold_font)
|
|
203
|
+
subtitle_label2.grid(row=row, column=3, columnspan=3, pady=(0, 15))
|
|
164
204
|
row += 1
|
|
165
205
|
|
|
166
206
|
# Create labels and entry fields for each additional parameter
|
|
@@ -175,27 +215,61 @@ class InterpolationEditor:
|
|
|
175
215
|
self.entry_degree = tk.Entry(main_frame, width=10)
|
|
176
216
|
self.entry_degree.insert(0, self.degree)
|
|
177
217
|
self.entry_degree.grid(row=row, column=1, sticky="w")
|
|
218
|
+
row -= 1
|
|
219
|
+
label = tk.Label(main_frame, text="Size:")
|
|
220
|
+
label.grid(row=row, column=3, sticky="e", padx=(0, 10))
|
|
221
|
+
self.entry_maskfill_size = tk.Entry(main_frame, width=10)
|
|
222
|
+
self.entry_maskfill_size.insert(0, self.maskfill_size)
|
|
223
|
+
self.entry_maskfill_size.grid(row=row, column=4, sticky="w")
|
|
224
|
+
label = tk.Label(main_frame, text="Operator:")
|
|
225
|
+
label.grid(row=row, column=5, sticky="e", padx=(0, 10))
|
|
226
|
+
self.entry_maskfill_operator = ttk.Combobox(
|
|
227
|
+
main_frame, width=10, values=MASKFILL_OPERATOR_VALUES, state="readonly"
|
|
228
|
+
)
|
|
229
|
+
self.entry_maskfill_operator.set(self.maskfill_operator)
|
|
230
|
+
# self.entry_maskfill_operator = tk.Entry(main_frame, width=10)
|
|
231
|
+
# self.entry_maskfill_operator.insert(0, self.maskfill_operator)
|
|
232
|
+
self.entry_maskfill_operator.grid(row=row, column=6, sticky="w")
|
|
178
233
|
row += 1
|
|
234
|
+
label = tk.Label(main_frame, text="Smooth:")
|
|
235
|
+
label.grid(row=row, column=3, sticky="e", padx=(0, 10))
|
|
236
|
+
self.entry_maskfill_smooth = tk.Entry(main_frame, width=10)
|
|
237
|
+
self.entry_maskfill_smooth.insert(0, str(self.maskfill_smooth))
|
|
238
|
+
self.entry_maskfill_smooth.grid(row=row, column=4, sticky="w")
|
|
239
|
+
label = tk.Label(main_frame, text="Verbose:")
|
|
240
|
+
label.grid(row=row, column=5, sticky="e", padx=(0, 10))
|
|
241
|
+
self.entry_maskfill_verbose = tk.Entry(main_frame, width=10)
|
|
242
|
+
self.entry_maskfill_verbose.insert(0, str(self.maskfill_verbose))
|
|
243
|
+
self.entry_maskfill_verbose.grid(row=row, column=6, sticky="w")
|
|
244
|
+
row += 1
|
|
245
|
+
|
|
246
|
+
# Vertical separator
|
|
247
|
+
separatorv1 = ttk.Separator(main_frame, orient="vertical")
|
|
248
|
+
separatorv1.grid(row=row - 3, column=2, rowspan=3, sticky="ns", padx=5)
|
|
179
249
|
|
|
180
250
|
# Separator
|
|
181
251
|
separator2 = ttk.Separator(main_frame, orient="horizontal")
|
|
182
|
-
separator2.grid(row=row, column=0, columnspan=
|
|
252
|
+
separator2.grid(row=row, column=0, columnspan=7, sticky="ew", pady=(10, 10))
|
|
183
253
|
row += 1
|
|
184
254
|
|
|
185
255
|
# Subtitle for region to be examined
|
|
186
256
|
subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=bold_font)
|
|
187
|
-
subtitle_label.grid(row=row, column=0, columnspan=
|
|
257
|
+
subtitle_label.grid(row=row, column=0, columnspan=7, pady=(0, 15))
|
|
188
258
|
row += 1
|
|
189
259
|
|
|
190
260
|
# Region to be examined label and entries
|
|
191
261
|
for key in ["xmin", "xmax", "ymin", "ymax"]:
|
|
192
262
|
# Parameter name label
|
|
193
|
-
label = tk.Label(main_frame, text=f"{key}:", anchor="e"
|
|
194
|
-
|
|
263
|
+
label = tk.Label(main_frame, text=f"{key}:", anchor="e")
|
|
264
|
+
if key in ["xmin", "xmax"]:
|
|
265
|
+
coloff = 0
|
|
266
|
+
else:
|
|
267
|
+
coloff = 4
|
|
268
|
+
label.grid(row=row, column=coloff, sticky="e", pady=5)
|
|
195
269
|
# Entry field
|
|
196
270
|
entry = tk.Entry(main_frame, width=10)
|
|
197
271
|
entry.insert(0, str(self.__dict__[key]))
|
|
198
|
-
entry.grid(row=row, column=1, padx=10, pady=5)
|
|
272
|
+
entry.grid(row=row, column=coloff + 1, padx=10, pady=5)
|
|
199
273
|
self.entries[key] = entry # dictionary to hold entry widgets
|
|
200
274
|
# Type label
|
|
201
275
|
dumtext = "(int)"
|
|
@@ -204,32 +278,42 @@ class InterpolationEditor:
|
|
|
204
278
|
else:
|
|
205
279
|
dumtext += f" --> [1, {self.imgshape[0]}]"
|
|
206
280
|
type_label = tk.Label(main_frame, text=dumtext, fg="gray", anchor="w", width=15)
|
|
207
|
-
type_label.grid(row=row, column=2, sticky="w", pady=5)
|
|
208
|
-
|
|
281
|
+
type_label.grid(row=row, column=coloff + 2, sticky="w", pady=5)
|
|
282
|
+
if key == "xmax":
|
|
283
|
+
row -= 1
|
|
284
|
+
else:
|
|
285
|
+
row += 1
|
|
286
|
+
|
|
287
|
+
# Vertical separator
|
|
288
|
+
separatorv2 = ttk.Separator(main_frame, orient="vertical")
|
|
289
|
+
separatorv2.grid(row=row - 2, column=3, rowspan=2, sticky="ns", padx=5)
|
|
209
290
|
|
|
210
291
|
# Separator
|
|
211
292
|
separator3 = ttk.Separator(main_frame, orient="horizontal")
|
|
212
|
-
separator3.grid(row=row, column=0, columnspan=
|
|
293
|
+
separator3.grid(row=row, column=0, columnspan=7, sticky="ew", pady=(10, 10))
|
|
213
294
|
row += 1
|
|
214
295
|
|
|
215
296
|
# Button frame
|
|
216
297
|
self.button_frame = tk.Frame(main_frame)
|
|
217
|
-
self.button_frame.grid(row=row, column=0, columnspan=
|
|
298
|
+
self.button_frame.grid(row=row, column=0, columnspan=7, pady=(5, 0))
|
|
218
299
|
|
|
219
300
|
# OK button
|
|
220
|
-
self.ok_button =
|
|
301
|
+
self.ok_button = ttk.Button(self.button_frame, text="OK", command=self.on_ok)
|
|
221
302
|
self.ok_button.pack(side="left", padx=5)
|
|
222
303
|
|
|
223
304
|
# Cancel button
|
|
224
|
-
self.cancel_button =
|
|
305
|
+
self.cancel_button = ttk.Button(self.button_frame, text="Cancel", command=self.on_cancel)
|
|
225
306
|
self.cancel_button.pack(side="left", padx=5)
|
|
226
307
|
|
|
308
|
+
# Initial focus on OK button
|
|
309
|
+
self.ok_button.focus_set()
|
|
310
|
+
|
|
227
311
|
# Initial action depending on the default method
|
|
228
312
|
self.action_on_method_change()
|
|
229
313
|
|
|
230
314
|
def on_ok(self):
|
|
231
315
|
"""Handle the OK button click event."""
|
|
232
|
-
self.cleaning_method =
|
|
316
|
+
self.cleaning_method = VALID_CLEANING_METHODS[self.cleaning_method_var.get()]
|
|
233
317
|
try:
|
|
234
318
|
self.npoints = int(self.entry_npoints.get())
|
|
235
319
|
except ValueError:
|
|
@@ -252,6 +336,44 @@ class InterpolationEditor:
|
|
|
252
336
|
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x and y interpolation.")
|
|
253
337
|
return
|
|
254
338
|
|
|
339
|
+
try:
|
|
340
|
+
self.maskfill_size = int(self.entry_maskfill_size.get())
|
|
341
|
+
except ValueError:
|
|
342
|
+
messagebox.showerror("Input Error", "Maskfill size must be an integer.")
|
|
343
|
+
return
|
|
344
|
+
if self.maskfill_size < 1:
|
|
345
|
+
messagebox.showerror("Input Error", "Maskfill size must be at least 1.")
|
|
346
|
+
return
|
|
347
|
+
if self.maskfill_size % 2 == 0:
|
|
348
|
+
messagebox.showerror("Input Error", "Maskfill size must be an odd integer.")
|
|
349
|
+
return
|
|
350
|
+
|
|
351
|
+
self.maskfill_operator = self.entry_maskfill_operator.get().strip()
|
|
352
|
+
if not self.maskfill_operator:
|
|
353
|
+
messagebox.showerror("Input Error", "Maskfill operator cannot be empty.")
|
|
354
|
+
return
|
|
355
|
+
if self.maskfill_operator not in ["median", "mean"]:
|
|
356
|
+
messagebox.showerror("Input Error", "Maskfill operator must be 'median' or 'mean'.")
|
|
357
|
+
return
|
|
358
|
+
|
|
359
|
+
smooth_str = self.entry_maskfill_smooth.get().strip().lower()
|
|
360
|
+
if smooth_str == "true":
|
|
361
|
+
self.maskfill_smooth = True
|
|
362
|
+
elif smooth_str == "false":
|
|
363
|
+
self.maskfill_smooth = False
|
|
364
|
+
else:
|
|
365
|
+
messagebox.showerror("Input Error", "Maskfill smooth must be True or False.")
|
|
366
|
+
return
|
|
367
|
+
|
|
368
|
+
verbose_str = self.entry_maskfill_verbose.get().strip().lower()
|
|
369
|
+
if verbose_str == "true":
|
|
370
|
+
self.maskfill_verbose = True
|
|
371
|
+
elif verbose_str == "false":
|
|
372
|
+
self.maskfill_verbose = False
|
|
373
|
+
else:
|
|
374
|
+
messagebox.showerror("Input Error", "Maskfill verbose must be True or False.")
|
|
375
|
+
return
|
|
376
|
+
|
|
255
377
|
# Retrieve and validate region parameters
|
|
256
378
|
try:
|
|
257
379
|
xmin = int(self.entries["xmin"].get())
|
|
@@ -303,31 +425,55 @@ class InterpolationEditor:
|
|
|
303
425
|
def action_on_method_change(self):
|
|
304
426
|
"""Handle changes in the selected cleaning method."""
|
|
305
427
|
selected_method = self.cleaning_method_var.get()
|
|
306
|
-
print(f"Selected cleaning method: {selected_method}")
|
|
428
|
+
print(f"Selected cleaning method: [red bold]{selected_method}[/red bold]")
|
|
307
429
|
if selected_method in ["x interp.", "y interp."]:
|
|
308
430
|
self.entry_npoints.config(state="normal")
|
|
309
431
|
self.entry_degree.config(state="normal")
|
|
432
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
433
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
434
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
435
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
310
436
|
elif selected_method == "surface interp.":
|
|
311
437
|
self.entry_npoints.config(state="normal")
|
|
312
438
|
self.entry_degree.config(state="disabled")
|
|
439
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
440
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
441
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
442
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
313
443
|
elif selected_method == "median":
|
|
314
444
|
self.entry_npoints.config(state="normal")
|
|
315
445
|
self.entry_degree.config(state="disabled")
|
|
446
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
447
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
448
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
449
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
316
450
|
elif selected_method == "mean":
|
|
317
451
|
self.entry_npoints.config(state="normal")
|
|
318
452
|
self.entry_degree.config(state="disabled")
|
|
453
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
454
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
455
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
456
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
319
457
|
elif selected_method == "lacosmic":
|
|
320
458
|
self.entry_npoints.config(state="disabled")
|
|
321
459
|
self.entry_degree.config(state="disabled")
|
|
460
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
461
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
462
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
463
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
322
464
|
elif selected_method == "auxdata":
|
|
323
465
|
self.entry_npoints.config(state="disabled")
|
|
324
466
|
self.entry_degree.config(state="disabled")
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
467
|
+
self.entry_maskfill_size.config(state="disabled")
|
|
468
|
+
self.entry_maskfill_operator.config(state="disabled")
|
|
469
|
+
self.entry_maskfill_smooth.config(state="disabled")
|
|
470
|
+
self.entry_maskfill_verbose.config(state="disabled")
|
|
471
|
+
elif selected_method == "maskfill":
|
|
472
|
+
self.entry_npoints.config(state="disabled")
|
|
473
|
+
self.entry_degree.config(state="disabled")
|
|
474
|
+
self.entry_maskfill_size.config(state="normal")
|
|
475
|
+
self.entry_maskfill_operator.config(state="normal")
|
|
476
|
+
self.entry_maskfill_smooth.config(state="normal")
|
|
477
|
+
self.entry_maskfill_verbose.config(state="normal")
|
|
478
|
+
else:
|
|
479
|
+
messagebox.showerror("Error", f"Unknown cleaning method selected: {selected_method}")
|
|
@@ -18,14 +18,19 @@ except ModuleNotFoundError as e:
|
|
|
18
18
|
"`pip install teareduce[cleanest]`."
|
|
19
19
|
) from e
|
|
20
20
|
import numpy as np
|
|
21
|
+
from rich import print
|
|
21
22
|
|
|
23
|
+
from .definitions import VALID_LACOSMIC_PSFMODEL_VALUES
|
|
24
|
+
from .gausskernel2d_elliptical import gausskernel2d_elliptical
|
|
22
25
|
|
|
23
|
-
|
|
26
|
+
|
|
27
|
+
def lacosmicpad(pad_width, show_arguments=False, **kwargs):
|
|
24
28
|
"""Execute LACosmic algorithm on a padded array.
|
|
25
29
|
|
|
26
30
|
This function pads the input image array before applying the LACosmic
|
|
27
31
|
cosmic ray cleaning algorithm. After processing, the padding is removed
|
|
28
|
-
to return an array of the original size.
|
|
32
|
+
to return an array of the original size. If `inbkg` or `invar` arrays
|
|
33
|
+
are provided, they are also padded accordingly.
|
|
29
34
|
|
|
30
35
|
The padding helps to mitigate edge effects that can occur during the
|
|
31
36
|
cosmic ray detection and cleaning process.
|
|
@@ -39,6 +44,8 @@ def lacosmicpad(pad_width, **kwargs):
|
|
|
39
44
|
pad_width : int
|
|
40
45
|
Width of the padding to be applied to the image before executing
|
|
41
46
|
the LACosmic algorithm.
|
|
47
|
+
show_arguments : bool
|
|
48
|
+
If True, display LACosmic arguments being employed.
|
|
42
49
|
**kwargs : dict
|
|
43
50
|
Keyword arguments to be passed to the `cosmicray_lacosmic` function.
|
|
44
51
|
|
|
@@ -49,6 +56,7 @@ def lacosmicpad(pad_width, **kwargs):
|
|
|
49
56
|
mask_array : 2D numpy.ndarray of bool
|
|
50
57
|
The mask array indicating detected cosmic rays.
|
|
51
58
|
"""
|
|
59
|
+
# Check for required 'ccd' argument
|
|
52
60
|
if "ccd" not in kwargs:
|
|
53
61
|
raise ValueError("The 'ccd' keyword argument must be provided.")
|
|
54
62
|
array = kwargs.pop("ccd")
|
|
@@ -56,6 +64,91 @@ def lacosmicpad(pad_width, **kwargs):
|
|
|
56
64
|
raise TypeError("The 'ccd' keyword argument must be a numpy ndarray.")
|
|
57
65
|
# Pad the array
|
|
58
66
|
padded_array = np.pad(array, pad_width, mode="reflect")
|
|
67
|
+
|
|
68
|
+
# Pad inbkg if provided
|
|
69
|
+
if "inbkg" in kwargs:
|
|
70
|
+
if kwargs["inbkg"] is not None:
|
|
71
|
+
inbkg = kwargs["inbkg"]
|
|
72
|
+
if not isinstance(inbkg, np.ndarray):
|
|
73
|
+
raise TypeError("The 'inbkg' keyword argument must be a numpy ndarray.")
|
|
74
|
+
kwargs["inbkg"] = np.pad(inbkg, pad_width, mode="reflect")
|
|
75
|
+
else:
|
|
76
|
+
kwargs["inbkg"] = None
|
|
77
|
+
|
|
78
|
+
# Pad invar if provided
|
|
79
|
+
if "invar" in kwargs:
|
|
80
|
+
if kwargs["invar"] is not None:
|
|
81
|
+
invar = kwargs["invar"]
|
|
82
|
+
if not isinstance(invar, np.ndarray):
|
|
83
|
+
raise TypeError("The 'invar' keyword argument must be a numpy ndarray.")
|
|
84
|
+
kwargs["invar"] = np.pad(invar, pad_width, mode="reflect")
|
|
85
|
+
else:
|
|
86
|
+
kwargs["invar"] = None
|
|
87
|
+
|
|
88
|
+
# check for fsmode
|
|
89
|
+
if "fsmode" not in kwargs:
|
|
90
|
+
raise ValueError("The 'fsmode' keyword argument must be provided.")
|
|
91
|
+
else:
|
|
92
|
+
fsmode = kwargs["fsmode"]
|
|
93
|
+
if fsmode == "convolve":
|
|
94
|
+
if "psfmodel" not in kwargs:
|
|
95
|
+
raise ValueError("The 'psfmodel' keyword argument must be provided when fsmode is 'convolve'.")
|
|
96
|
+
psfmodel = kwargs["psfmodel"]
|
|
97
|
+
if psfmodel not in VALID_LACOSMIC_PSFMODEL_VALUES:
|
|
98
|
+
raise ValueError(f"The 'psfmodel' keyword argument must be one of {VALID_LACOSMIC_PSFMODEL_VALUES}.")
|
|
99
|
+
if "psffwhm" in kwargs:
|
|
100
|
+
raise ValueError(
|
|
101
|
+
"When 'fsmode' is 'convolve', 'psffwhm' should not be provided; use 'psffwhm_x' and 'psffwhm_y' instead."
|
|
102
|
+
)
|
|
103
|
+
if "psffwhm_x" not in kwargs or "psffwhm_y" not in kwargs:
|
|
104
|
+
raise ValueError("When 'fsmode' is 'convolve', both 'psffwhm_x' and 'psffwhm_y' must be provided.")
|
|
105
|
+
fwhm_x = kwargs["psffwhm_x"]
|
|
106
|
+
fwhm_y = kwargs["psffwhm_y"]
|
|
107
|
+
if "psfsize" not in kwargs:
|
|
108
|
+
raise ValueError("When 'fsmode' is 'convolve', 'psfsize' must be provided.")
|
|
109
|
+
psfsize = kwargs["psfsize"]
|
|
110
|
+
if kwargs["psfmodel"] == "gaussxy":
|
|
111
|
+
if "psfk" in kwargs:
|
|
112
|
+
raise ValueError(
|
|
113
|
+
"When 'fsmode' is 'convolve' and 'psfmodel' is 'gaussxy', 'psfk' should not be provided; it will be generated from 'psffwhm_x' and 'psffwhm_y'."
|
|
114
|
+
)
|
|
115
|
+
kwargs["psfk"] = gausskernel2d_elliptical(fwhm_x, fwhm_y, psfsize)
|
|
116
|
+
if show_arguments:
|
|
117
|
+
print(
|
|
118
|
+
f"Generated elliptical Gaussian kernel with fwhm_x={fwhm_x}, fwhm_y={fwhm_y}, size={psfsize}."
|
|
119
|
+
)
|
|
120
|
+
elif kwargs["psfmodel"] in ["gauss", "moffat"]:
|
|
121
|
+
kwargs["psffwhm"] = (fwhm_x + fwhm_y) / 2.0 # average for circular psf
|
|
122
|
+
if show_arguments:
|
|
123
|
+
print(f"Set psffwhm to average of fwhm_x and fwhm_y: {kwargs['psffwhm']}.")
|
|
124
|
+
elif kwargs["psfmodel"] == "gaussx":
|
|
125
|
+
kwargs["psffwhm"] = fwhm_x
|
|
126
|
+
if show_arguments:
|
|
127
|
+
print(f"Set psffwhm to fwhm_x: {fwhm_x}.")
|
|
128
|
+
elif kwargs["psfmodel"] == "gaussy":
|
|
129
|
+
kwargs["psffwhm"] = fwhm_y
|
|
130
|
+
if show_arguments:
|
|
131
|
+
print(f"Set psffwhm to fwhm_y: {fwhm_y}.")
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(f"Unsupported psfmodel: {kwargs['psfmodel']}")
|
|
134
|
+
if show_arguments:
|
|
135
|
+
print("Deleting 'psffwhm_x' and 'psffwhm_y' from kwargs.")
|
|
136
|
+
del kwargs["psffwhm_x"]
|
|
137
|
+
del kwargs["psffwhm_y"]
|
|
138
|
+
elif fsmode == "median":
|
|
139
|
+
# Remove unnecessary parameters for median fsmode
|
|
140
|
+
for param in ["psfmodel", "psfsize", "psffwhm", "psffwhm_x", "psffwhm_y"]:
|
|
141
|
+
if param in kwargs:
|
|
142
|
+
if show_arguments:
|
|
143
|
+
print(f"Removing '{param}' argument since fsmode is 'median'.")
|
|
144
|
+
del kwargs[param]
|
|
145
|
+
else:
|
|
146
|
+
raise ValueError("The 'fsmode' keyword argument must be either 'convolve' or 'median'.")
|
|
147
|
+
|
|
148
|
+
# Show LACosmic arguments if requested
|
|
149
|
+
if show_arguments:
|
|
150
|
+
for key, value in kwargs.items():
|
|
151
|
+
print(f"LACosmic parameter: {key} = {value}")
|
|
59
152
|
# Apply LACosmic algorithm to the padded array
|
|
60
153
|
cleaned_padded_array, mask_padded_array = cosmicray_lacosmic(ccd=padded_array, **kwargs)
|
|
61
154
|
# Remove padding
|
teareduce/cleanest/mergemasks.py
CHANGED
|
@@ -11,9 +11,10 @@
|
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
from scipy import ndimage
|
|
14
|
+
from rich import print
|
|
14
15
|
|
|
15
16
|
|
|
16
|
-
def merge_peak_tail_masks(mask_peaks, mask_tails):
|
|
17
|
+
def merge_peak_tail_masks(mask_peaks, mask_tails, verbose=False):
|
|
17
18
|
"""Merge peak and tail masks for cosmic ray cleaning.
|
|
18
19
|
|
|
19
20
|
Tail pixels are preserved only if they correspond to CR features
|
|
@@ -25,6 +26,8 @@ def merge_peak_tail_masks(mask_peaks, mask_tails):
|
|
|
25
26
|
Boolean array indicating the pixels identified as cosmic ray peaks.
|
|
26
27
|
mask_tails : ndarray
|
|
27
28
|
Boolean array indicating the pixels identified as cosmic ray tails.
|
|
29
|
+
verbose: bool
|
|
30
|
+
If True, print additional information during processing.
|
|
28
31
|
|
|
29
32
|
Returns
|
|
30
33
|
-------
|
|
@@ -41,6 +44,10 @@ def merge_peak_tail_masks(mask_peaks, mask_tails):
|
|
|
41
44
|
if mask_peaks.dtype != bool or mask_tails.dtype != bool:
|
|
42
45
|
raise TypeError("Input masks must be boolean arrays.")
|
|
43
46
|
|
|
47
|
+
if verbose:
|
|
48
|
+
ndigits = np.max([len(str(np.sum(mask_peaks))), len(str(np.sum(mask_tails)))])
|
|
49
|
+
print(f"Number of cosmic ray pixels (peaks)...............: {np.sum(mask_peaks):{ndigits}d}")
|
|
50
|
+
print(f"Number of cosmic ray pixels (tails)...............: {np.sum(mask_tails):{ndigits}d}")
|
|
44
51
|
# find structures in tail mask
|
|
45
52
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
46
53
|
cr_labels_tails, num_crs_tails = ndimage.label(mask_tails, structure=structure)
|
|
@@ -55,4 +62,7 @@ def merge_peak_tail_masks(mask_peaks, mask_tails):
|
|
|
55
62
|
if icr > 0:
|
|
56
63
|
merged_mask[cr_labels_tails == icr] = True
|
|
57
64
|
|
|
65
|
+
if verbose:
|
|
66
|
+
print(f"Number of cosmic ray pixels (merged peaks & tails): {np.sum(merged_mask):{ndigits}d}")
|
|
67
|
+
|
|
58
68
|
return merged_mask
|