teareduce 0.5.4__py3-none-any.whl → 0.5.5__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 +57 -8
- teareduce/cleanest/centerchildparent.py +46 -0
- teareduce/cleanest/cleanest.py +16 -19
- teareduce/cleanest/cosmicraycleanerapp.py +262 -170
- teareduce/cleanest/definitions.py +30 -29
- teareduce/cleanest/imagedisplay.py +8 -9
- teareduce/cleanest/interpolation_a.py +5 -9
- teareduce/cleanest/interpolationeditor.py +156 -35
- teareduce/cleanest/modalprogressbar.py +182 -0
- teareduce/cleanest/parametereditor.py +98 -76
- teareduce/cleanest/reviewcosmicray.py +90 -62
- teareduce/version.py +1 -1
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/METADATA +1 -1
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/RECORD +18 -16
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/WHEEL +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.5.dist-info}/top_level.txt +0 -0
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
import tkinter as tk
|
|
13
13
|
from tkinter import filedialog
|
|
14
|
+
from tkinter import font as tkfont
|
|
14
15
|
from tkinter import messagebox
|
|
15
16
|
import sys
|
|
16
17
|
|
|
@@ -23,12 +24,16 @@ from scipy import ndimage
|
|
|
23
24
|
import numpy as np
|
|
24
25
|
import os
|
|
25
26
|
from rich import print
|
|
26
|
-
from tqdm import tqdm
|
|
27
27
|
|
|
28
|
+
from .centerchildparent import center_on_parent
|
|
28
29
|
from .definitions import lacosmic_default_dict
|
|
29
30
|
from .definitions import DEFAULT_NPOINTS_INTERP
|
|
30
31
|
from .definitions import DEFAULT_DEGREE_INTERP
|
|
31
32
|
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
33
|
+
from .definitions import DEFAULT_TK_WINDOW_SIZE_X
|
|
34
|
+
from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
|
|
35
|
+
from .definitions import DEFAULT_FONT_FAMILY
|
|
36
|
+
from .definitions import DEFAULT_FONT_SIZE
|
|
32
37
|
from .dilatemask import dilatemask
|
|
33
38
|
from .find_closest_true import find_closest_true
|
|
34
39
|
from .interpolation_a import interpolation_a
|
|
@@ -38,19 +43,32 @@ from .interpolationeditor import InterpolationEditor
|
|
|
38
43
|
from .imagedisplay import ImageDisplay
|
|
39
44
|
from .parametereditor import ParameterEditor
|
|
40
45
|
from .reviewcosmicray import ReviewCosmicRay
|
|
46
|
+
from .modalprogressbar import ModalProgressBar
|
|
41
47
|
|
|
42
48
|
from ..imshow import imshow
|
|
43
49
|
from ..sliceregion import SliceRegion2D
|
|
44
50
|
from ..zscale import zscale
|
|
45
51
|
|
|
46
52
|
import matplotlib
|
|
53
|
+
|
|
47
54
|
matplotlib.use("TkAgg")
|
|
48
55
|
|
|
49
56
|
|
|
50
57
|
class CosmicRayCleanerApp(ImageDisplay):
|
|
51
58
|
"""Main application class for cosmic ray cleaning."""
|
|
52
59
|
|
|
53
|
-
def __init__(
|
|
60
|
+
def __init__(
|
|
61
|
+
self,
|
|
62
|
+
root,
|
|
63
|
+
input_fits,
|
|
64
|
+
extension=0,
|
|
65
|
+
auxfile=None,
|
|
66
|
+
extension_auxfile=0,
|
|
67
|
+
fontfamily=DEFAULT_FONT_FAMILY,
|
|
68
|
+
fontsize=DEFAULT_FONT_SIZE,
|
|
69
|
+
width=DEFAULT_TK_WINDOW_SIZE_X,
|
|
70
|
+
height=DEFAULT_TK_WINDOW_SIZE_Y,
|
|
71
|
+
):
|
|
54
72
|
"""
|
|
55
73
|
Initialize the application.
|
|
56
74
|
|
|
@@ -66,6 +84,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
66
84
|
Path to an auxiliary FITS file (default is None).
|
|
67
85
|
extension_auxfile : int, optional
|
|
68
86
|
FITS extension for auxiliary file (default is 0).
|
|
87
|
+
fontfamily : str, optional
|
|
88
|
+
Font family for the GUI (default is "Helvetica").
|
|
89
|
+
fontsize : int, optional
|
|
90
|
+
Font size for the GUI (default is 14).
|
|
69
91
|
|
|
70
92
|
Methods
|
|
71
93
|
-------
|
|
@@ -96,6 +118,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
96
118
|
----------
|
|
97
119
|
root : tk.Tk
|
|
98
120
|
The main Tkinter window.
|
|
121
|
+
fontfamily : str
|
|
122
|
+
Font family for the GUI.
|
|
123
|
+
fontsize : int
|
|
124
|
+
Font size for the GUI.
|
|
125
|
+
default_font : tkfont.Font
|
|
126
|
+
The default font used in the GUI.
|
|
99
127
|
lacosmic_params : dict
|
|
100
128
|
Dictionary of L.A.Cosmic parameters.
|
|
101
129
|
input_fits : str
|
|
@@ -116,12 +144,16 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
116
144
|
Boolean mask of detected cosmic ray pixels.
|
|
117
145
|
last_xmin : int
|
|
118
146
|
Last used minimum x-coordinate for region selection.
|
|
147
|
+
From 1 to NAXIS1.
|
|
119
148
|
last_xmax : int
|
|
120
149
|
Last used maximum x-coordinate for region selection.
|
|
150
|
+
From 1 to NAXIS1.
|
|
121
151
|
last_ymin : int
|
|
122
152
|
Last used minimum y-coordinate for region selection.
|
|
153
|
+
From 1 to NAXIS2.
|
|
123
154
|
last_ymax : int
|
|
124
155
|
Last used maximum y-coordinate for region selection.
|
|
156
|
+
From 1 to NAXIS2.
|
|
125
157
|
last_npoints : int
|
|
126
158
|
Last used number of points for interpolation.
|
|
127
159
|
last_degree : int
|
|
@@ -137,10 +169,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
137
169
|
"""
|
|
138
170
|
self.root = root
|
|
139
171
|
# self.root.geometry("800x800+50+0") # This does not work in Fedora
|
|
140
|
-
self.
|
|
172
|
+
self.width = width
|
|
173
|
+
self.height = height
|
|
174
|
+
self.root.minsize(self.width, self.height)
|
|
141
175
|
self.root.update_idletasks()
|
|
142
|
-
self.root.geometry("+50+0")
|
|
143
176
|
self.root.title("Cosmic Ray Cleaner")
|
|
177
|
+
self.fontfamily = fontfamily
|
|
178
|
+
self.fontsize = fontsize
|
|
179
|
+
self.default_font = tkfont.nametofont("TkDefaultFont")
|
|
180
|
+
self.default_font.configure(
|
|
181
|
+
family=fontfamily, size=fontsize, weight="normal", slant="roman", underline=0, overstrike=0
|
|
182
|
+
)
|
|
144
183
|
self.lacosmic_params = lacosmic_default_dict.copy()
|
|
145
184
|
self.input_fits = input_fits
|
|
146
185
|
self.extension = extension
|
|
@@ -178,21 +217,21 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
178
217
|
The loaded data is stored in `self.data` and `self.auxdata` attributes.
|
|
179
218
|
"""
|
|
180
219
|
try:
|
|
181
|
-
with fits.open(self.input_fits, mode=
|
|
220
|
+
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
182
221
|
self.data = hdul[self.extension].data
|
|
183
|
-
if
|
|
184
|
-
self.mask_fixed = hdul[
|
|
222
|
+
if "CRMASK" in hdul:
|
|
223
|
+
self.mask_fixed = hdul["CRMASK"].data.astype(bool)
|
|
185
224
|
else:
|
|
186
225
|
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
187
226
|
except Exception as e:
|
|
188
227
|
print(f"Error loading FITS file: {e}")
|
|
189
228
|
self.mask_crfound = np.zeros(self.data.shape, dtype=bool)
|
|
190
229
|
naxis2, naxis1 = self.data.shape
|
|
191
|
-
self.region = SliceRegion2D(f
|
|
230
|
+
self.region = SliceRegion2D(f"[1:{naxis1}, 1:{naxis2}]", mode="fits").python
|
|
192
231
|
# Read auxiliary file if provided
|
|
193
232
|
if self.auxfile is not None:
|
|
194
233
|
try:
|
|
195
|
-
with fits.open(self.auxfile, mode=
|
|
234
|
+
with fits.open(self.auxfile, mode="readonly") as hdul_aux:
|
|
196
235
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
197
236
|
if self.auxdata.shape != self.data.shape:
|
|
198
237
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -229,15 +268,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
229
268
|
title="Save cleaned FITS file",
|
|
230
269
|
defaultextension=".fits",
|
|
231
270
|
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
232
|
-
initialfile=suggested_name
|
|
271
|
+
initialfile=suggested_name,
|
|
233
272
|
)
|
|
234
273
|
try:
|
|
235
|
-
with fits.open(self.input_fits, mode=
|
|
274
|
+
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
236
275
|
hdul[self.extension].data = self.data
|
|
237
|
-
if
|
|
238
|
-
hdul[
|
|
276
|
+
if "CRMASK" in hdul:
|
|
277
|
+
hdul["CRMASK"].data = self.mask_fixed.astype(np.uint8)
|
|
239
278
|
else:
|
|
240
|
-
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name=
|
|
279
|
+
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name="CRMASK")
|
|
241
280
|
hdul.append(crmask_hdu)
|
|
242
281
|
hdul.writeto(output_fits, overwrite=True)
|
|
243
282
|
print(f"Cleaned data saved to {output_fits}")
|
|
@@ -270,18 +309,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
270
309
|
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
271
310
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
272
311
|
if self.overplot_cr_pixels:
|
|
273
|
-
self.overplot_cr_button = tk.Button(
|
|
274
|
-
|
|
312
|
+
self.overplot_cr_button = tk.Button(
|
|
313
|
+
self.button_frame1, text="CR overlay: On", command=self.toggle_cr_overlay
|
|
314
|
+
)
|
|
275
315
|
else:
|
|
276
|
-
self.overplot_cr_button = tk.Button(
|
|
277
|
-
|
|
316
|
+
self.overplot_cr_button = tk.Button(
|
|
317
|
+
self.button_frame1, text="CR overlay: Off", command=self.toggle_cr_overlay
|
|
318
|
+
)
|
|
278
319
|
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
279
|
-
self.apply_lacosmic_button = tk.Button(
|
|
280
|
-
|
|
320
|
+
self.apply_lacosmic_button = tk.Button(
|
|
321
|
+
self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
|
|
322
|
+
)
|
|
281
323
|
self.apply_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
282
324
|
self.apply_lacosmic_button.config(state=tk.DISABLED) # Initially disabled
|
|
283
|
-
self.examine_detected_cr_button = tk.Button(
|
|
284
|
-
|
|
325
|
+
self.examine_detected_cr_button = tk.Button(
|
|
326
|
+
self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
|
|
327
|
+
)
|
|
285
328
|
self.examine_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
286
329
|
self.examine_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
287
330
|
|
|
@@ -308,16 +351,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
308
351
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
309
352
|
|
|
310
353
|
# Figure
|
|
311
|
-
self.
|
|
312
|
-
self.
|
|
313
|
-
|
|
354
|
+
self.plot_frame = tk.Frame(self.root)
|
|
355
|
+
self.plot_frame.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
|
|
356
|
+
fig_dpi = 100
|
|
357
|
+
image_ratio = 480 / 640 # Default image ratio
|
|
358
|
+
fig_width_inches = self.width / fig_dpi
|
|
359
|
+
fig_height_inches = self.height * image_ratio / fig_dpi
|
|
360
|
+
self.fig, self.ax = plt.subplots(figsize=(fig_width_inches, fig_height_inches), dpi=fig_dpi)
|
|
361
|
+
self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
|
|
362
|
+
canvas_widget = self.canvas.get_tk_widget()
|
|
363
|
+
canvas_widget.config(width=self.width, height=self.height * image_ratio)
|
|
364
|
+
canvas_widget.pack(expand=True)
|
|
314
365
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
315
366
|
self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
|
|
316
367
|
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
317
368
|
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
318
369
|
canvas_widget = self.canvas.get_tk_widget()
|
|
319
|
-
# canvas_widget.pack(fill=tk.BOTH, expand=True) # This does not work in Fedora
|
|
320
|
-
canvas_widget.pack(expand=True)
|
|
321
370
|
|
|
322
371
|
# Matplotlib toolbar
|
|
323
372
|
self.toolbar_frame = tk.Frame(self.root)
|
|
@@ -326,13 +375,20 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
326
375
|
self.toolbar.update()
|
|
327
376
|
|
|
328
377
|
# update the image display
|
|
329
|
-
xlabel =
|
|
330
|
-
ylabel =
|
|
378
|
+
xlabel = "X pixel (from 1 to NAXIS1)"
|
|
379
|
+
ylabel = "Y pixel (from 1 to NAXIS2)"
|
|
331
380
|
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
332
|
-
self.image, _, _ = imshow(
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
381
|
+
self.image, _, _ = imshow(
|
|
382
|
+
self.fig,
|
|
383
|
+
self.ax,
|
|
384
|
+
self.data,
|
|
385
|
+
vmin=vmin,
|
|
386
|
+
vmax=vmax,
|
|
387
|
+
title=os.path.basename(self.input_fits),
|
|
388
|
+
xlabel=xlabel,
|
|
389
|
+
ylabel=ylabel,
|
|
390
|
+
extent=extent,
|
|
391
|
+
)
|
|
336
392
|
self.fig.tight_layout()
|
|
337
393
|
|
|
338
394
|
def run_lacosmic(self):
|
|
@@ -340,15 +396,16 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
340
396
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
341
397
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
342
398
|
editor_window = tk.Toplevel(self.root)
|
|
399
|
+
center_on_parent(child=editor_window, parent=self.root)
|
|
343
400
|
editor = ParameterEditor(
|
|
344
401
|
root=editor_window,
|
|
345
402
|
param_dict=self.lacosmic_params,
|
|
346
|
-
window_title=
|
|
403
|
+
window_title="Cosmic Ray Mask Generation Parameters",
|
|
347
404
|
xmin=self.last_xmin,
|
|
348
405
|
xmax=self.last_xmax,
|
|
349
406
|
ymin=self.last_ymin,
|
|
350
407
|
ymax=self.last_ymax,
|
|
351
|
-
imgshape=self.data.shape
|
|
408
|
+
imgshape=self.data.shape,
|
|
352
409
|
)
|
|
353
410
|
# Make it modal (blocks interaction with main window)
|
|
354
411
|
editor_window.transient(self.root)
|
|
@@ -359,12 +416,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
359
416
|
updated_params = editor.get_result()
|
|
360
417
|
if updated_params is not None:
|
|
361
418
|
# Update last used region values
|
|
362
|
-
self.last_xmin = updated_params[
|
|
363
|
-
self.last_xmax = updated_params[
|
|
364
|
-
self.last_ymin = updated_params[
|
|
365
|
-
self.last_ymax = updated_params[
|
|
366
|
-
usefulregion = SliceRegion2D(
|
|
367
|
-
|
|
419
|
+
self.last_xmin = updated_params["xmin"]["value"]
|
|
420
|
+
self.last_xmax = updated_params["xmax"]["value"]
|
|
421
|
+
self.last_ymin = updated_params["ymin"]["value"]
|
|
422
|
+
self.last_ymax = updated_params["ymax"]["value"]
|
|
423
|
+
usefulregion = SliceRegion2D(
|
|
424
|
+
f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
|
|
425
|
+
).python
|
|
368
426
|
usefulmask = np.zeros_like(self.data)
|
|
369
427
|
usefulmask[usefulregion] = 1.0
|
|
370
428
|
# Update parameter dictionary with new values
|
|
@@ -372,34 +430,42 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
372
430
|
print("Parameters updated:")
|
|
373
431
|
for key, info in self.lacosmic_params.items():
|
|
374
432
|
print(f" {key}: {info['value']}")
|
|
375
|
-
if self.lacosmic_params[
|
|
433
|
+
if self.lacosmic_params["nruns"]["value"] not in [1, 2]:
|
|
376
434
|
raise ValueError("nruns must be 1 or 2")
|
|
377
435
|
# Execute L.A.Cosmic with updated parameters
|
|
436
|
+
print("[bold green]Executing L.A.Cosmic (run 1)...[/bold green]")
|
|
437
|
+
borderpadd = updated_params["borderpadd"]["value"]
|
|
438
|
+
data_reflection_padded = np.pad(self.data, pad_width=borderpadd, mode="reflect")
|
|
378
439
|
cleandata_lacosmic, mask_crfound = cosmicray_lacosmic(
|
|
379
|
-
|
|
380
|
-
gain=self.lacosmic_params[
|
|
381
|
-
readnoise=self.lacosmic_params[
|
|
382
|
-
sigclip=self.lacosmic_params[
|
|
383
|
-
sigfrac=self.lacosmic_params[
|
|
384
|
-
objlim=self.lacosmic_params[
|
|
385
|
-
niter=self.lacosmic_params[
|
|
386
|
-
verbose=self.lacosmic_params[
|
|
440
|
+
ccd=data_reflection_padded,
|
|
441
|
+
gain=self.lacosmic_params["run1_gain"]["value"],
|
|
442
|
+
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
443
|
+
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
444
|
+
sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
|
|
445
|
+
objlim=self.lacosmic_params["run1_objlim"]["value"],
|
|
446
|
+
niter=self.lacosmic_params["run1_niter"]["value"],
|
|
447
|
+
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
387
448
|
)
|
|
449
|
+
cleandata_lacosmic = cleandata_lacosmic[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
450
|
+
mask_crfound = mask_crfound[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
388
451
|
# Apply usefulmask to consider only selected region
|
|
389
452
|
cleandata_lacosmic *= usefulmask
|
|
390
453
|
mask_crfound = mask_crfound & (usefulmask.astype(bool))
|
|
391
454
|
# Second execution if nruns == 2
|
|
392
|
-
if self.lacosmic_params[
|
|
455
|
+
if self.lacosmic_params["nruns"]["value"] == 2:
|
|
456
|
+
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
393
457
|
cleandata_lacosmic2, mask_crfound2 = cosmicray_lacosmic(
|
|
394
|
-
|
|
395
|
-
gain=self.lacosmic_params[
|
|
396
|
-
readnoise=self.lacosmic_params[
|
|
397
|
-
sigclip=self.lacosmic_params[
|
|
398
|
-
sigfrac=self.lacosmic_params[
|
|
399
|
-
objlim=self.lacosmic_params[
|
|
400
|
-
niter=self.lacosmic_params[
|
|
401
|
-
verbose=self.lacosmic_params[
|
|
458
|
+
ccd=data_reflection_padded,
|
|
459
|
+
gain=self.lacosmic_params["run2_gain"]["value"],
|
|
460
|
+
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
461
|
+
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
462
|
+
sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
|
|
463
|
+
objlim=self.lacosmic_params["run2_objlim"]["value"],
|
|
464
|
+
niter=self.lacosmic_params["run2_niter"]["value"],
|
|
465
|
+
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
402
466
|
)
|
|
467
|
+
cleandata_lacosmic2 = cleandata_lacosmic2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
468
|
+
mask_crfound2 = mask_crfound2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
403
469
|
# Apply usefulmask to consider only selected region
|
|
404
470
|
cleandata_lacosmic2 *= usefulmask
|
|
405
471
|
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
@@ -420,7 +486,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
420
486
|
for icr in np.unique(cr_labels2_preserved):
|
|
421
487
|
if icr > 0:
|
|
422
488
|
mask_crfound[cr_labels2 == icr] = True
|
|
423
|
-
print(f
|
|
489
|
+
print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
|
|
424
490
|
# Use the cleandata from the second run
|
|
425
491
|
cleandata_lacosmic = cleandata_lacosmic2
|
|
426
492
|
# Select the image region to process
|
|
@@ -431,29 +497,31 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
431
497
|
# Process the mask: dilation and labeling
|
|
432
498
|
if np.any(self.mask_crfound):
|
|
433
499
|
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
434
|
-
dilation = self.lacosmic_params[
|
|
500
|
+
dilation = self.lacosmic_params["dilation"]["value"]
|
|
435
501
|
if dilation > 0:
|
|
436
502
|
# Dilate the mask by the specified number of pixels
|
|
437
503
|
self.mask_crfound = dilatemask(
|
|
438
|
-
mask=self.mask_crfound,
|
|
439
|
-
iterations=self.lacosmic_params['dilation']['value'],
|
|
440
|
-
connectivity=1
|
|
504
|
+
mask=self.mask_crfound, iterations=self.lacosmic_params["dilation"]["value"], connectivity=1
|
|
441
505
|
)
|
|
442
506
|
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
443
507
|
sdum = str(num_cr_pixels_after_dilation)
|
|
444
508
|
else:
|
|
445
509
|
sdum = str(num_cr_pixels_before_dilation)
|
|
446
|
-
print(
|
|
447
|
-
|
|
510
|
+
print(
|
|
511
|
+
"Number of cosmic ray pixels detected by L.A.Cosmic: "
|
|
512
|
+
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
513
|
+
)
|
|
448
514
|
if dilation > 0:
|
|
449
|
-
print(
|
|
450
|
-
|
|
515
|
+
print(
|
|
516
|
+
f"Number of cosmic ray pixels after dilation........: "
|
|
517
|
+
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
518
|
+
)
|
|
451
519
|
# Label connected components in the mask; note that by default,
|
|
452
520
|
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
453
521
|
# diagonal connections too, so we define a 3x3 square.
|
|
454
522
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
455
523
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
456
|
-
print(f"Number of cosmic
|
|
524
|
+
print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
|
|
457
525
|
self.apply_lacosmic_button.config(state=tk.NORMAL)
|
|
458
526
|
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
459
527
|
self.update_cr_overlay()
|
|
@@ -480,16 +548,16 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
480
548
|
"""Update the overlay of cosmic ray pixels on the image."""
|
|
481
549
|
if self.overplot_cr_pixels:
|
|
482
550
|
# Remove previous CR pixel overlay (if any)
|
|
483
|
-
if hasattr(self,
|
|
551
|
+
if hasattr(self, "scatter_cr"):
|
|
484
552
|
self.scatter_cr.remove()
|
|
485
553
|
del self.scatter_cr
|
|
486
554
|
# Overlay CR pixels in red
|
|
487
555
|
if np.any(self.mask_crfound):
|
|
488
556
|
y_indices, x_indices = np.where(self.mask_crfound)
|
|
489
|
-
self.scatter_cr = self.ax.scatter(x_indices + 1, y_indices + 1, s=1, c=
|
|
557
|
+
self.scatter_cr = self.ax.scatter(x_indices + 1, y_indices + 1, s=1, c="red", marker="o")
|
|
490
558
|
else:
|
|
491
559
|
# Remove CR pixel overlay
|
|
492
|
-
if hasattr(self,
|
|
560
|
+
if hasattr(self, "scatter_cr"):
|
|
493
561
|
self.scatter_cr.remove()
|
|
494
562
|
del self.scatter_cr
|
|
495
563
|
self.canvas.draw_idle()
|
|
@@ -498,21 +566,25 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
498
566
|
"""Apply the selected cleaning method to the detected cosmic rays."""
|
|
499
567
|
if np.any(self.mask_crfound):
|
|
500
568
|
# recalculate labels and number of features
|
|
501
|
-
structure = [[1, 1, 1],
|
|
502
|
-
[1, 1, 1],
|
|
503
|
-
[1, 1, 1]]
|
|
569
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
504
570
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
505
571
|
sdum = str(np.sum(self.mask_crfound))
|
|
506
572
|
print(f"Number of cosmic ray pixels detected by L.A.Cosmic...........: {sdum}")
|
|
507
573
|
print(f"Number of cosmic rays (grouped pixels) detected by L.A.Cosmic: {self.num_features:>{len(sdum)}}")
|
|
508
574
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
509
575
|
editor_window = tk.Toplevel(self.root)
|
|
576
|
+
center_on_parent(child=editor_window, parent=self.root)
|
|
510
577
|
editor = InterpolationEditor(
|
|
511
578
|
root=editor_window,
|
|
512
|
-
last_dilation=self.lacosmic_params[
|
|
579
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
513
580
|
last_npoints=self.last_npoints,
|
|
514
581
|
last_degree=self.last_degree,
|
|
515
582
|
auxdata=self.auxdata,
|
|
583
|
+
xmin=self.last_xmin,
|
|
584
|
+
xmax=self.last_xmax,
|
|
585
|
+
ymin=self.last_ymin,
|
|
586
|
+
ymax=self.last_ymax,
|
|
587
|
+
imgshape=self.data.shape,
|
|
516
588
|
)
|
|
517
589
|
# Make it modal (blocks interaction with main window)
|
|
518
590
|
editor_window.transient(self.root)
|
|
@@ -521,98 +593,119 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
521
593
|
self.root.wait_window(editor_window)
|
|
522
594
|
# Get the result after window closes
|
|
523
595
|
cleaning_method = editor.cleaning_method
|
|
524
|
-
num_cr_cleaned = 0
|
|
525
596
|
if cleaning_method is None:
|
|
526
597
|
print("Interpolation method selection cancelled. No cleaning applied!")
|
|
527
598
|
return
|
|
528
599
|
self.last_npoints = editor.npoints
|
|
529
600
|
self.last_degree = editor.degree
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
601
|
+
cleaning_region = SliceRegion2D(
|
|
602
|
+
f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
|
|
603
|
+
).python
|
|
604
|
+
print(
|
|
605
|
+
"Applying cleaning method to region "
|
|
606
|
+
f"x=[{editor.xmin},{editor.xmax}], y=[{editor.ymin},{editor.ymax}]"
|
|
607
|
+
)
|
|
608
|
+
mask_crfound_region = np.zeros_like(self.mask_crfound, dtype=bool)
|
|
609
|
+
mask_crfound_region[cleaning_region] = self.mask_crfound[cleaning_region]
|
|
610
|
+
data_has_been_modified = False
|
|
611
|
+
if np.any(mask_crfound_region):
|
|
612
|
+
if cleaning_method == "lacosmic":
|
|
613
|
+
# Replace detected CR pixels with L.A.Cosmic values
|
|
614
|
+
self.data[mask_crfound_region] = self.cleandata_lacosmic[mask_crfound_region]
|
|
615
|
+
# update mask_fixed to include the newly fixed pixels
|
|
616
|
+
self.mask_fixed[mask_crfound_region] = True
|
|
617
|
+
# upate mask_crfound by eliminating the cleaned pixels
|
|
618
|
+
self.mask_crfound[mask_crfound_region] = False
|
|
619
|
+
data_has_been_modified = True
|
|
620
|
+
elif cleaning_method == "auxdata":
|
|
621
|
+
if self.auxdata is None:
|
|
622
|
+
print("No auxiliary data available. Cleaning skipped!")
|
|
623
|
+
return
|
|
624
|
+
# Replace detected CR pixels with auxiliary data values
|
|
625
|
+
self.data[mask_crfound_region] = self.auxdata[mask_crfound_region]
|
|
626
|
+
# update mask_fixed to include the newly fixed pixels
|
|
627
|
+
self.mask_fixed[mask_crfound_region] = True
|
|
628
|
+
# upate mask_crfound by eliminating the cleaned pixels
|
|
629
|
+
self.mask_crfound[mask_crfound_region] = False
|
|
630
|
+
data_has_been_modified = True
|
|
631
|
+
else:
|
|
632
|
+
# Determine features to process within the selected region
|
|
633
|
+
features_in_region = np.unique(self.cr_labels[mask_crfound_region])
|
|
634
|
+
with ModalProgressBar(
|
|
635
|
+
parent=self.root, iterable=range(1, self.num_features + 1), desc="Cleaning cosmic rays"
|
|
636
|
+
) as pbar:
|
|
637
|
+
for i in pbar:
|
|
638
|
+
if i in features_in_region:
|
|
639
|
+
tmp_mask_fixed = np.zeros_like(self.data, dtype=bool)
|
|
640
|
+
if cleaning_method == "x":
|
|
641
|
+
interpolation_performed, _, _ = interpolation_x(
|
|
642
|
+
data=self.data,
|
|
643
|
+
mask_fixed=tmp_mask_fixed,
|
|
644
|
+
cr_labels=self.cr_labels,
|
|
645
|
+
cr_index=i,
|
|
646
|
+
npoints=editor.npoints,
|
|
647
|
+
degree=editor.degree,
|
|
648
|
+
)
|
|
649
|
+
elif cleaning_method == "y":
|
|
650
|
+
interpolation_performed, _, _ = interpolation_y(
|
|
651
|
+
data=self.data,
|
|
652
|
+
mask_fixed=tmp_mask_fixed,
|
|
653
|
+
cr_labels=self.cr_labels,
|
|
654
|
+
cr_index=i,
|
|
655
|
+
npoints=editor.npoints,
|
|
656
|
+
degree=editor.degree,
|
|
657
|
+
)
|
|
658
|
+
elif cleaning_method == "a-plane":
|
|
659
|
+
interpolation_performed, _, _ = interpolation_a(
|
|
660
|
+
data=self.data,
|
|
661
|
+
mask_fixed=tmp_mask_fixed,
|
|
662
|
+
cr_labels=self.cr_labels,
|
|
663
|
+
cr_index=i,
|
|
664
|
+
npoints=editor.npoints,
|
|
665
|
+
method="surface",
|
|
666
|
+
)
|
|
667
|
+
elif cleaning_method == "a-median":
|
|
668
|
+
interpolation_performed, _, _ = interpolation_a(
|
|
669
|
+
data=self.data,
|
|
670
|
+
mask_fixed=tmp_mask_fixed,
|
|
671
|
+
cr_labels=self.cr_labels,
|
|
672
|
+
cr_index=i,
|
|
673
|
+
npoints=editor.npoints,
|
|
674
|
+
method="median",
|
|
675
|
+
)
|
|
676
|
+
elif cleaning_method == "a-mean":
|
|
677
|
+
interpolation_performed, _, _ = interpolation_a(
|
|
678
|
+
data=self.data,
|
|
679
|
+
mask_fixed=tmp_mask_fixed,
|
|
680
|
+
cr_labels=self.cr_labels,
|
|
681
|
+
cr_index=i,
|
|
682
|
+
npoints=editor.npoints,
|
|
683
|
+
method="mean",
|
|
684
|
+
)
|
|
685
|
+
else:
|
|
686
|
+
raise ValueError(f"Unknown cleaning method: {cleaning_method}")
|
|
687
|
+
if interpolation_performed:
|
|
688
|
+
# update mask_fixed to include the newly fixed pixels
|
|
689
|
+
self.mask_fixed[tmp_mask_fixed] = True
|
|
690
|
+
# upate mask_crfound by eliminating the cleaned pixels
|
|
691
|
+
self.mask_crfound[tmp_mask_fixed] = False
|
|
692
|
+
# mark that data has been modified
|
|
693
|
+
data_has_been_modified = True
|
|
694
|
+
# If any pixels were cleaned, print message
|
|
695
|
+
if data_has_been_modified:
|
|
696
|
+
print("Cosmic ray cleaning applied.")
|
|
549
697
|
else:
|
|
550
|
-
|
|
551
|
-
tmp_mask_fixed = np.zeros_like(self.data, dtype=bool)
|
|
552
|
-
if cleaning_method == 'x':
|
|
553
|
-
interpolation_performed, _, _ = interpolation_x(
|
|
554
|
-
data=self.data,
|
|
555
|
-
mask_fixed=tmp_mask_fixed,
|
|
556
|
-
cr_labels=self.cr_labels,
|
|
557
|
-
cr_index=i,
|
|
558
|
-
npoints=editor.npoints,
|
|
559
|
-
degree=editor.degree
|
|
560
|
-
)
|
|
561
|
-
elif cleaning_method == 'y':
|
|
562
|
-
interpolation_performed, _, _ = interpolation_y(
|
|
563
|
-
data=self.data,
|
|
564
|
-
mask_fixed=tmp_mask_fixed,
|
|
565
|
-
cr_labels=self.cr_labels,
|
|
566
|
-
cr_index=i,
|
|
567
|
-
npoints=editor.npoints,
|
|
568
|
-
degree=editor.degree
|
|
569
|
-
)
|
|
570
|
-
elif cleaning_method == 'a-plane':
|
|
571
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
572
|
-
data=self.data,
|
|
573
|
-
mask_fixed=tmp_mask_fixed,
|
|
574
|
-
cr_labels=self.cr_labels,
|
|
575
|
-
cr_index=i,
|
|
576
|
-
npoints=editor.npoints,
|
|
577
|
-
method='surface'
|
|
578
|
-
)
|
|
579
|
-
elif cleaning_method == 'a-median':
|
|
580
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
581
|
-
data=self.data,
|
|
582
|
-
mask_fixed=tmp_mask_fixed,
|
|
583
|
-
cr_labels=self.cr_labels,
|
|
584
|
-
cr_index=i,
|
|
585
|
-
npoints=editor.npoints,
|
|
586
|
-
method='median'
|
|
587
|
-
)
|
|
588
|
-
elif cleaning_method == 'a-mean':
|
|
589
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
590
|
-
data=self.data,
|
|
591
|
-
mask_fixed=tmp_mask_fixed,
|
|
592
|
-
cr_labels=self.cr_labels,
|
|
593
|
-
cr_index=i,
|
|
594
|
-
npoints=editor.npoints,
|
|
595
|
-
method='mean'
|
|
596
|
-
)
|
|
597
|
-
else:
|
|
598
|
-
raise ValueError(f"Unknown cleaning method: {cleaning_method}")
|
|
599
|
-
if interpolation_performed:
|
|
600
|
-
num_cr_cleaned += 1
|
|
601
|
-
# update mask_fixed to include the newly fixed pixels
|
|
602
|
-
self.mask_fixed[tmp_mask_fixed] = True
|
|
603
|
-
# upate mask_crfound by eliminating the cleaned pixels
|
|
604
|
-
self.mask_crfound[tmp_mask_fixed] = False
|
|
605
|
-
print(f"Number of cosmic rays identified and cleaned: {num_cr_cleaned}")
|
|
698
|
+
print("No cosmic ray pixels cleaned.")
|
|
606
699
|
# recalculate labels and number of features
|
|
607
700
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
608
701
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
609
702
|
sdum = str(np.sum(self.mask_crfound))
|
|
610
|
-
print(f"Remaining number of cosmic ray pixels
|
|
611
|
-
print(f"Remaining number of cosmic
|
|
703
|
+
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
704
|
+
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
612
705
|
# redraw image to show the changes
|
|
613
706
|
self.image.set_data(self.data)
|
|
614
707
|
self.canvas.draw_idle()
|
|
615
|
-
if
|
|
708
|
+
if data_has_been_modified:
|
|
616
709
|
self.save_button.config(state=tk.NORMAL)
|
|
617
710
|
if self.num_features == 0:
|
|
618
711
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
@@ -623,6 +716,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
623
716
|
"""Open a window to examine and possibly clean detected cosmic rays."""
|
|
624
717
|
self.working_in_review_window = True
|
|
625
718
|
review_window = tk.Toplevel(self.root)
|
|
719
|
+
center_on_parent(child=review_window, parent=self.root)
|
|
626
720
|
if ixpix is not None and iypix is not None:
|
|
627
721
|
# select single pixel based on provided coordinates
|
|
628
722
|
tmp_cr_labels = np.zeros_like(self.data, dtype=int)
|
|
@@ -636,9 +730,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
636
730
|
num_features=1,
|
|
637
731
|
first_cr_index=1,
|
|
638
732
|
single_cr=True,
|
|
639
|
-
last_dilation=self.lacosmic_params[
|
|
733
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
640
734
|
last_npoints=self.last_npoints,
|
|
641
|
-
last_degree=self.last_degree
|
|
735
|
+
last_degree=self.last_degree,
|
|
642
736
|
)
|
|
643
737
|
else:
|
|
644
738
|
review = ReviewCosmicRay(
|
|
@@ -650,9 +744,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
650
744
|
num_features=self.num_features,
|
|
651
745
|
first_cr_index=first_cr_index,
|
|
652
746
|
single_cr=single_cr,
|
|
653
|
-
last_dilation=self.lacosmic_params[
|
|
747
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
654
748
|
last_npoints=self.last_npoints,
|
|
655
|
-
last_degree=self.last_degree
|
|
749
|
+
last_degree=self.last_degree,
|
|
656
750
|
)
|
|
657
751
|
# Make it modal (blocks interaction with main window)
|
|
658
752
|
review_window.transient(self.root)
|
|
@@ -672,8 +766,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
672
766
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
673
767
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
674
768
|
sdum = str(np.sum(self.mask_crfound))
|
|
675
|
-
print(f"Remaining number of cosmic ray pixels
|
|
676
|
-
print(f"Remaining number of cosmic
|
|
769
|
+
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
770
|
+
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
677
771
|
# redraw image to show the changes
|
|
678
772
|
self.image.set_data(self.data)
|
|
679
773
|
self.canvas.draw_idle()
|
|
@@ -687,12 +781,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
687
781
|
def stop_app(self):
|
|
688
782
|
"""Stop the application, prompting to save if there are unsaved changes."""
|
|
689
783
|
proceed_with_stop = True
|
|
690
|
-
if self.save_button[
|
|
784
|
+
if self.save_button["state"] == tk.NORMAL:
|
|
691
785
|
print("Warning: There are unsaved changes!")
|
|
692
786
|
proceed_with_stop = messagebox.askyesno(
|
|
693
|
-
"Unsaved Changes",
|
|
694
|
-
"You have unsaved changes.\nDo you really want to quit?",
|
|
695
|
-
default=messagebox.NO
|
|
787
|
+
"Unsaved Changes", "You have unsaved changes.\nDo you really want to quit?", default=messagebox.NO
|
|
696
788
|
)
|
|
697
789
|
if proceed_with_stop:
|
|
698
790
|
self.root.quit()
|
|
@@ -700,11 +792,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
700
792
|
|
|
701
793
|
def on_key(self, event):
|
|
702
794
|
"""Handle key press events."""
|
|
703
|
-
if event.key ==
|
|
795
|
+
if event.key == "q":
|
|
704
796
|
pass # Ignore the "q" key to prevent closing the window
|
|
705
|
-
elif event.key ==
|
|
797
|
+
elif event.key == ",":
|
|
706
798
|
self.set_minmax()
|
|
707
|
-
elif event.key ==
|
|
799
|
+
elif event.key == "/":
|
|
708
800
|
self.set_zscale()
|
|
709
801
|
else:
|
|
710
802
|
print(f"Key pressed: {event.key}")
|
|
@@ -753,8 +845,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
753
845
|
imin = (iy - 1) - semiwidth if (iy - 1) - semiwidth >= 0 else 0
|
|
754
846
|
imax = (iy - 1) + semiwidth if (iy - 1) + semiwidth < self.data.shape[0] else self.data.shape[0] - 1
|
|
755
847
|
ijmax = np.unravel_index(
|
|
756
|
-
np.argmax(self.data[imin:imax+1, jmin:jmax+1]),
|
|
757
|
-
self.data[imin:imax+1, jmin:jmax+1].shape
|
|
848
|
+
np.argmax(self.data[imin : imax + 1, jmin : jmax + 1]),
|
|
849
|
+
self.data[imin : imax + 1, jmin : jmax + 1].shape,
|
|
758
850
|
)
|
|
759
851
|
ixpix = ijmax[1] + jmin + 1
|
|
760
852
|
iypix = ijmax[0] + imin + 1
|