teareduce 0.5.3__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 +264 -168
- 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 +99 -66
- teareduce/version.py +1 -1
- {teareduce-0.5.3.dist-info → teareduce-0.5.5.dist-info}/METADATA +1 -1
- {teareduce-0.5.3.dist-info → teareduce-0.5.5.dist-info}/RECORD +18 -16
- {teareduce-0.5.3.dist-info → teareduce-0.5.5.dist-info}/WHEEL +0 -0
- {teareduce-0.5.3.dist-info → teareduce-0.5.5.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.3.dist-info → teareduce-0.5.5.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.3.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
|
|
@@ -136,8 +168,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
136
168
|
Flag to indicate if the review window is active.
|
|
137
169
|
"""
|
|
138
170
|
self.root = root
|
|
171
|
+
# self.root.geometry("800x800+50+0") # This does not work in Fedora
|
|
172
|
+
self.width = width
|
|
173
|
+
self.height = height
|
|
174
|
+
self.root.minsize(self.width, self.height)
|
|
175
|
+
self.root.update_idletasks()
|
|
139
176
|
self.root.title("Cosmic Ray Cleaner")
|
|
140
|
-
self.
|
|
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
|
+
)
|
|
141
183
|
self.lacosmic_params = lacosmic_default_dict.copy()
|
|
142
184
|
self.input_fits = input_fits
|
|
143
185
|
self.extension = extension
|
|
@@ -175,21 +217,21 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
175
217
|
The loaded data is stored in `self.data` and `self.auxdata` attributes.
|
|
176
218
|
"""
|
|
177
219
|
try:
|
|
178
|
-
with fits.open(self.input_fits, mode=
|
|
220
|
+
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
179
221
|
self.data = hdul[self.extension].data
|
|
180
|
-
if
|
|
181
|
-
self.mask_fixed = hdul[
|
|
222
|
+
if "CRMASK" in hdul:
|
|
223
|
+
self.mask_fixed = hdul["CRMASK"].data.astype(bool)
|
|
182
224
|
else:
|
|
183
225
|
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
184
226
|
except Exception as e:
|
|
185
227
|
print(f"Error loading FITS file: {e}")
|
|
186
228
|
self.mask_crfound = np.zeros(self.data.shape, dtype=bool)
|
|
187
229
|
naxis2, naxis1 = self.data.shape
|
|
188
|
-
self.region = SliceRegion2D(f
|
|
230
|
+
self.region = SliceRegion2D(f"[1:{naxis1}, 1:{naxis2}]", mode="fits").python
|
|
189
231
|
# Read auxiliary file if provided
|
|
190
232
|
if self.auxfile is not None:
|
|
191
233
|
try:
|
|
192
|
-
with fits.open(self.auxfile, mode=
|
|
234
|
+
with fits.open(self.auxfile, mode="readonly") as hdul_aux:
|
|
193
235
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
194
236
|
if self.auxdata.shape != self.data.shape:
|
|
195
237
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -226,15 +268,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
226
268
|
title="Save cleaned FITS file",
|
|
227
269
|
defaultextension=".fits",
|
|
228
270
|
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
229
|
-
initialfile=suggested_name
|
|
271
|
+
initialfile=suggested_name,
|
|
230
272
|
)
|
|
231
273
|
try:
|
|
232
|
-
with fits.open(self.input_fits, mode=
|
|
274
|
+
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
233
275
|
hdul[self.extension].data = self.data
|
|
234
|
-
if
|
|
235
|
-
hdul[
|
|
276
|
+
if "CRMASK" in hdul:
|
|
277
|
+
hdul["CRMASK"].data = self.mask_fixed.astype(np.uint8)
|
|
236
278
|
else:
|
|
237
|
-
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name=
|
|
279
|
+
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name="CRMASK")
|
|
238
280
|
hdul.append(crmask_hdu)
|
|
239
281
|
hdul.writeto(output_fits, overwrite=True)
|
|
240
282
|
print(f"Cleaned data saved to {output_fits}")
|
|
@@ -267,18 +309,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
267
309
|
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
268
310
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
269
311
|
if self.overplot_cr_pixels:
|
|
270
|
-
self.overplot_cr_button = tk.Button(
|
|
271
|
-
|
|
312
|
+
self.overplot_cr_button = tk.Button(
|
|
313
|
+
self.button_frame1, text="CR overlay: On", command=self.toggle_cr_overlay
|
|
314
|
+
)
|
|
272
315
|
else:
|
|
273
|
-
self.overplot_cr_button = tk.Button(
|
|
274
|
-
|
|
316
|
+
self.overplot_cr_button = tk.Button(
|
|
317
|
+
self.button_frame1, text="CR overlay: Off", command=self.toggle_cr_overlay
|
|
318
|
+
)
|
|
275
319
|
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
276
|
-
self.apply_lacosmic_button = tk.Button(
|
|
277
|
-
|
|
320
|
+
self.apply_lacosmic_button = tk.Button(
|
|
321
|
+
self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
|
|
322
|
+
)
|
|
278
323
|
self.apply_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
279
324
|
self.apply_lacosmic_button.config(state=tk.DISABLED) # Initially disabled
|
|
280
|
-
self.examine_detected_cr_button = tk.Button(
|
|
281
|
-
|
|
325
|
+
self.examine_detected_cr_button = tk.Button(
|
|
326
|
+
self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
|
|
327
|
+
)
|
|
282
328
|
self.examine_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
283
329
|
self.examine_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
284
330
|
|
|
@@ -305,15 +351,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
305
351
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
306
352
|
|
|
307
353
|
# Figure
|
|
308
|
-
self.
|
|
309
|
-
self.
|
|
310
|
-
|
|
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)
|
|
311
365
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
312
366
|
self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
|
|
313
367
|
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
314
368
|
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
315
369
|
canvas_widget = self.canvas.get_tk_widget()
|
|
316
|
-
canvas_widget.pack(fill=tk.BOTH, expand=True)
|
|
317
370
|
|
|
318
371
|
# Matplotlib toolbar
|
|
319
372
|
self.toolbar_frame = tk.Frame(self.root)
|
|
@@ -322,13 +375,20 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
322
375
|
self.toolbar.update()
|
|
323
376
|
|
|
324
377
|
# update the image display
|
|
325
|
-
xlabel =
|
|
326
|
-
ylabel =
|
|
378
|
+
xlabel = "X pixel (from 1 to NAXIS1)"
|
|
379
|
+
ylabel = "Y pixel (from 1 to NAXIS2)"
|
|
327
380
|
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
328
|
-
self.image, _, _ = imshow(
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
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
|
+
)
|
|
332
392
|
self.fig.tight_layout()
|
|
333
393
|
|
|
334
394
|
def run_lacosmic(self):
|
|
@@ -336,15 +396,16 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
336
396
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
337
397
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
338
398
|
editor_window = tk.Toplevel(self.root)
|
|
399
|
+
center_on_parent(child=editor_window, parent=self.root)
|
|
339
400
|
editor = ParameterEditor(
|
|
340
401
|
root=editor_window,
|
|
341
402
|
param_dict=self.lacosmic_params,
|
|
342
|
-
window_title=
|
|
403
|
+
window_title="Cosmic Ray Mask Generation Parameters",
|
|
343
404
|
xmin=self.last_xmin,
|
|
344
405
|
xmax=self.last_xmax,
|
|
345
406
|
ymin=self.last_ymin,
|
|
346
407
|
ymax=self.last_ymax,
|
|
347
|
-
imgshape=self.data.shape
|
|
408
|
+
imgshape=self.data.shape,
|
|
348
409
|
)
|
|
349
410
|
# Make it modal (blocks interaction with main window)
|
|
350
411
|
editor_window.transient(self.root)
|
|
@@ -355,12 +416,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
355
416
|
updated_params = editor.get_result()
|
|
356
417
|
if updated_params is not None:
|
|
357
418
|
# Update last used region values
|
|
358
|
-
self.last_xmin = updated_params[
|
|
359
|
-
self.last_xmax = updated_params[
|
|
360
|
-
self.last_ymin = updated_params[
|
|
361
|
-
self.last_ymax = updated_params[
|
|
362
|
-
usefulregion = SliceRegion2D(
|
|
363
|
-
|
|
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
|
|
364
426
|
usefulmask = np.zeros_like(self.data)
|
|
365
427
|
usefulmask[usefulregion] = 1.0
|
|
366
428
|
# Update parameter dictionary with new values
|
|
@@ -368,34 +430,42 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
368
430
|
print("Parameters updated:")
|
|
369
431
|
for key, info in self.lacosmic_params.items():
|
|
370
432
|
print(f" {key}: {info['value']}")
|
|
371
|
-
if self.lacosmic_params[
|
|
433
|
+
if self.lacosmic_params["nruns"]["value"] not in [1, 2]:
|
|
372
434
|
raise ValueError("nruns must be 1 or 2")
|
|
373
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")
|
|
374
439
|
cleandata_lacosmic, mask_crfound = cosmicray_lacosmic(
|
|
375
|
-
|
|
376
|
-
gain=self.lacosmic_params[
|
|
377
|
-
readnoise=self.lacosmic_params[
|
|
378
|
-
sigclip=self.lacosmic_params[
|
|
379
|
-
sigfrac=self.lacosmic_params[
|
|
380
|
-
objlim=self.lacosmic_params[
|
|
381
|
-
niter=self.lacosmic_params[
|
|
382
|
-
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"],
|
|
383
448
|
)
|
|
449
|
+
cleandata_lacosmic = cleandata_lacosmic[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
450
|
+
mask_crfound = mask_crfound[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
384
451
|
# Apply usefulmask to consider only selected region
|
|
385
452
|
cleandata_lacosmic *= usefulmask
|
|
386
453
|
mask_crfound = mask_crfound & (usefulmask.astype(bool))
|
|
387
454
|
# Second execution if nruns == 2
|
|
388
|
-
if self.lacosmic_params[
|
|
455
|
+
if self.lacosmic_params["nruns"]["value"] == 2:
|
|
456
|
+
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
389
457
|
cleandata_lacosmic2, mask_crfound2 = cosmicray_lacosmic(
|
|
390
|
-
|
|
391
|
-
gain=self.lacosmic_params[
|
|
392
|
-
readnoise=self.lacosmic_params[
|
|
393
|
-
sigclip=self.lacosmic_params[
|
|
394
|
-
sigfrac=self.lacosmic_params[
|
|
395
|
-
objlim=self.lacosmic_params[
|
|
396
|
-
niter=self.lacosmic_params[
|
|
397
|
-
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"],
|
|
398
466
|
)
|
|
467
|
+
cleandata_lacosmic2 = cleandata_lacosmic2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
468
|
+
mask_crfound2 = mask_crfound2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
399
469
|
# Apply usefulmask to consider only selected region
|
|
400
470
|
cleandata_lacosmic2 *= usefulmask
|
|
401
471
|
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
@@ -416,7 +486,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
416
486
|
for icr in np.unique(cr_labels2_preserved):
|
|
417
487
|
if icr > 0:
|
|
418
488
|
mask_crfound[cr_labels2 == icr] = True
|
|
419
|
-
print(f
|
|
489
|
+
print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
|
|
420
490
|
# Use the cleandata from the second run
|
|
421
491
|
cleandata_lacosmic = cleandata_lacosmic2
|
|
422
492
|
# Select the image region to process
|
|
@@ -427,29 +497,31 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
427
497
|
# Process the mask: dilation and labeling
|
|
428
498
|
if np.any(self.mask_crfound):
|
|
429
499
|
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
430
|
-
dilation = self.lacosmic_params[
|
|
500
|
+
dilation = self.lacosmic_params["dilation"]["value"]
|
|
431
501
|
if dilation > 0:
|
|
432
502
|
# Dilate the mask by the specified number of pixels
|
|
433
503
|
self.mask_crfound = dilatemask(
|
|
434
|
-
mask=self.mask_crfound,
|
|
435
|
-
iterations=self.lacosmic_params['dilation']['value'],
|
|
436
|
-
connectivity=1
|
|
504
|
+
mask=self.mask_crfound, iterations=self.lacosmic_params["dilation"]["value"], connectivity=1
|
|
437
505
|
)
|
|
438
506
|
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
439
507
|
sdum = str(num_cr_pixels_after_dilation)
|
|
440
508
|
else:
|
|
441
509
|
sdum = str(num_cr_pixels_before_dilation)
|
|
442
|
-
print(
|
|
443
|
-
|
|
510
|
+
print(
|
|
511
|
+
"Number of cosmic ray pixels detected by L.A.Cosmic: "
|
|
512
|
+
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
513
|
+
)
|
|
444
514
|
if dilation > 0:
|
|
445
|
-
print(
|
|
446
|
-
|
|
515
|
+
print(
|
|
516
|
+
f"Number of cosmic ray pixels after dilation........: "
|
|
517
|
+
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
518
|
+
)
|
|
447
519
|
# Label connected components in the mask; note that by default,
|
|
448
520
|
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
449
521
|
# diagonal connections too, so we define a 3x3 square.
|
|
450
522
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
451
523
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
452
|
-
print(f"Number of cosmic
|
|
524
|
+
print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
|
|
453
525
|
self.apply_lacosmic_button.config(state=tk.NORMAL)
|
|
454
526
|
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
455
527
|
self.update_cr_overlay()
|
|
@@ -476,16 +548,16 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
476
548
|
"""Update the overlay of cosmic ray pixels on the image."""
|
|
477
549
|
if self.overplot_cr_pixels:
|
|
478
550
|
# Remove previous CR pixel overlay (if any)
|
|
479
|
-
if hasattr(self,
|
|
551
|
+
if hasattr(self, "scatter_cr"):
|
|
480
552
|
self.scatter_cr.remove()
|
|
481
553
|
del self.scatter_cr
|
|
482
554
|
# Overlay CR pixels in red
|
|
483
555
|
if np.any(self.mask_crfound):
|
|
484
556
|
y_indices, x_indices = np.where(self.mask_crfound)
|
|
485
|
-
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")
|
|
486
558
|
else:
|
|
487
559
|
# Remove CR pixel overlay
|
|
488
|
-
if hasattr(self,
|
|
560
|
+
if hasattr(self, "scatter_cr"):
|
|
489
561
|
self.scatter_cr.remove()
|
|
490
562
|
del self.scatter_cr
|
|
491
563
|
self.canvas.draw_idle()
|
|
@@ -494,21 +566,25 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
494
566
|
"""Apply the selected cleaning method to the detected cosmic rays."""
|
|
495
567
|
if np.any(self.mask_crfound):
|
|
496
568
|
# recalculate labels and number of features
|
|
497
|
-
structure = [[1, 1, 1],
|
|
498
|
-
[1, 1, 1],
|
|
499
|
-
[1, 1, 1]]
|
|
569
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
500
570
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
501
571
|
sdum = str(np.sum(self.mask_crfound))
|
|
502
572
|
print(f"Number of cosmic ray pixels detected by L.A.Cosmic...........: {sdum}")
|
|
503
573
|
print(f"Number of cosmic rays (grouped pixels) detected by L.A.Cosmic: {self.num_features:>{len(sdum)}}")
|
|
504
574
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
505
575
|
editor_window = tk.Toplevel(self.root)
|
|
576
|
+
center_on_parent(child=editor_window, parent=self.root)
|
|
506
577
|
editor = InterpolationEditor(
|
|
507
578
|
root=editor_window,
|
|
508
|
-
last_dilation=self.lacosmic_params[
|
|
579
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
509
580
|
last_npoints=self.last_npoints,
|
|
510
581
|
last_degree=self.last_degree,
|
|
511
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,
|
|
512
588
|
)
|
|
513
589
|
# Make it modal (blocks interaction with main window)
|
|
514
590
|
editor_window.transient(self.root)
|
|
@@ -517,98 +593,119 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
517
593
|
self.root.wait_window(editor_window)
|
|
518
594
|
# Get the result after window closes
|
|
519
595
|
cleaning_method = editor.cleaning_method
|
|
520
|
-
num_cr_cleaned = 0
|
|
521
596
|
if cleaning_method is None:
|
|
522
597
|
print("Interpolation method selection cancelled. No cleaning applied!")
|
|
523
598
|
return
|
|
524
599
|
self.last_npoints = editor.npoints
|
|
525
600
|
self.last_degree = editor.degree
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
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.")
|
|
545
697
|
else:
|
|
546
|
-
|
|
547
|
-
tmp_mask_fixed = np.zeros_like(self.data, dtype=bool)
|
|
548
|
-
if cleaning_method == 'x':
|
|
549
|
-
interpolation_performed, _, _ = interpolation_x(
|
|
550
|
-
data=self.data,
|
|
551
|
-
mask_fixed=tmp_mask_fixed,
|
|
552
|
-
cr_labels=self.cr_labels,
|
|
553
|
-
cr_index=i,
|
|
554
|
-
npoints=editor.npoints,
|
|
555
|
-
degree=editor.degree
|
|
556
|
-
)
|
|
557
|
-
elif cleaning_method == 'y':
|
|
558
|
-
interpolation_performed, _, _ = interpolation_y(
|
|
559
|
-
data=self.data,
|
|
560
|
-
mask_fixed=tmp_mask_fixed,
|
|
561
|
-
cr_labels=self.cr_labels,
|
|
562
|
-
cr_index=i,
|
|
563
|
-
npoints=editor.npoints,
|
|
564
|
-
degree=editor.degree
|
|
565
|
-
)
|
|
566
|
-
elif cleaning_method == 'a-plane':
|
|
567
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
568
|
-
data=self.data,
|
|
569
|
-
mask_fixed=tmp_mask_fixed,
|
|
570
|
-
cr_labels=self.cr_labels,
|
|
571
|
-
cr_index=i,
|
|
572
|
-
npoints=editor.npoints,
|
|
573
|
-
method='surface'
|
|
574
|
-
)
|
|
575
|
-
elif cleaning_method == 'a-median':
|
|
576
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
577
|
-
data=self.data,
|
|
578
|
-
mask_fixed=tmp_mask_fixed,
|
|
579
|
-
cr_labels=self.cr_labels,
|
|
580
|
-
cr_index=i,
|
|
581
|
-
npoints=editor.npoints,
|
|
582
|
-
method='median'
|
|
583
|
-
)
|
|
584
|
-
elif cleaning_method == 'a-mean':
|
|
585
|
-
interpolation_performed, _, _ = interpolation_a(
|
|
586
|
-
data=self.data,
|
|
587
|
-
mask_fixed=tmp_mask_fixed,
|
|
588
|
-
cr_labels=self.cr_labels,
|
|
589
|
-
cr_index=i,
|
|
590
|
-
npoints=editor.npoints,
|
|
591
|
-
method='mean'
|
|
592
|
-
)
|
|
593
|
-
else:
|
|
594
|
-
raise ValueError(f"Unknown cleaning method: {cleaning_method}")
|
|
595
|
-
if interpolation_performed:
|
|
596
|
-
num_cr_cleaned += 1
|
|
597
|
-
# update mask_fixed to include the newly fixed pixels
|
|
598
|
-
self.mask_fixed[tmp_mask_fixed] = True
|
|
599
|
-
# upate mask_crfound by eliminating the cleaned pixels
|
|
600
|
-
self.mask_crfound[tmp_mask_fixed] = False
|
|
601
|
-
print(f"Number of cosmic rays identified and cleaned: {num_cr_cleaned}")
|
|
698
|
+
print("No cosmic ray pixels cleaned.")
|
|
602
699
|
# recalculate labels and number of features
|
|
603
700
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
604
701
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
605
702
|
sdum = str(np.sum(self.mask_crfound))
|
|
606
|
-
print(f"Remaining number of cosmic ray pixels
|
|
607
|
-
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)}}")
|
|
608
705
|
# redraw image to show the changes
|
|
609
706
|
self.image.set_data(self.data)
|
|
610
707
|
self.canvas.draw_idle()
|
|
611
|
-
if
|
|
708
|
+
if data_has_been_modified:
|
|
612
709
|
self.save_button.config(state=tk.NORMAL)
|
|
613
710
|
if self.num_features == 0:
|
|
614
711
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
@@ -619,6 +716,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
619
716
|
"""Open a window to examine and possibly clean detected cosmic rays."""
|
|
620
717
|
self.working_in_review_window = True
|
|
621
718
|
review_window = tk.Toplevel(self.root)
|
|
719
|
+
center_on_parent(child=review_window, parent=self.root)
|
|
622
720
|
if ixpix is not None and iypix is not None:
|
|
623
721
|
# select single pixel based on provided coordinates
|
|
624
722
|
tmp_cr_labels = np.zeros_like(self.data, dtype=int)
|
|
@@ -632,9 +730,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
632
730
|
num_features=1,
|
|
633
731
|
first_cr_index=1,
|
|
634
732
|
single_cr=True,
|
|
635
|
-
last_dilation=self.lacosmic_params[
|
|
733
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
636
734
|
last_npoints=self.last_npoints,
|
|
637
|
-
last_degree=self.last_degree
|
|
735
|
+
last_degree=self.last_degree,
|
|
638
736
|
)
|
|
639
737
|
else:
|
|
640
738
|
review = ReviewCosmicRay(
|
|
@@ -646,9 +744,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
646
744
|
num_features=self.num_features,
|
|
647
745
|
first_cr_index=first_cr_index,
|
|
648
746
|
single_cr=single_cr,
|
|
649
|
-
last_dilation=self.lacosmic_params[
|
|
747
|
+
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
650
748
|
last_npoints=self.last_npoints,
|
|
651
|
-
last_degree=self.last_degree
|
|
749
|
+
last_degree=self.last_degree,
|
|
652
750
|
)
|
|
653
751
|
# Make it modal (blocks interaction with main window)
|
|
654
752
|
review_window.transient(self.root)
|
|
@@ -668,8 +766,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
668
766
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
669
767
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
670
768
|
sdum = str(np.sum(self.mask_crfound))
|
|
671
|
-
print(f"Remaining number of cosmic ray pixels
|
|
672
|
-
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)}}")
|
|
673
771
|
# redraw image to show the changes
|
|
674
772
|
self.image.set_data(self.data)
|
|
675
773
|
self.canvas.draw_idle()
|
|
@@ -683,12 +781,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
683
781
|
def stop_app(self):
|
|
684
782
|
"""Stop the application, prompting to save if there are unsaved changes."""
|
|
685
783
|
proceed_with_stop = True
|
|
686
|
-
if self.save_button[
|
|
784
|
+
if self.save_button["state"] == tk.NORMAL:
|
|
687
785
|
print("Warning: There are unsaved changes!")
|
|
688
786
|
proceed_with_stop = messagebox.askyesno(
|
|
689
|
-
"Unsaved Changes",
|
|
690
|
-
"You have unsaved changes.\nDo you really want to quit?",
|
|
691
|
-
default=messagebox.NO
|
|
787
|
+
"Unsaved Changes", "You have unsaved changes.\nDo you really want to quit?", default=messagebox.NO
|
|
692
788
|
)
|
|
693
789
|
if proceed_with_stop:
|
|
694
790
|
self.root.quit()
|
|
@@ -696,11 +792,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
696
792
|
|
|
697
793
|
def on_key(self, event):
|
|
698
794
|
"""Handle key press events."""
|
|
699
|
-
if event.key ==
|
|
795
|
+
if event.key == "q":
|
|
700
796
|
pass # Ignore the "q" key to prevent closing the window
|
|
701
|
-
elif event.key ==
|
|
797
|
+
elif event.key == ",":
|
|
702
798
|
self.set_minmax()
|
|
703
|
-
elif event.key ==
|
|
799
|
+
elif event.key == "/":
|
|
704
800
|
self.set_zscale()
|
|
705
801
|
else:
|
|
706
802
|
print(f"Key pressed: {event.key}")
|
|
@@ -749,8 +845,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
749
845
|
imin = (iy - 1) - semiwidth if (iy - 1) - semiwidth >= 0 else 0
|
|
750
846
|
imax = (iy - 1) + semiwidth if (iy - 1) + semiwidth < self.data.shape[0] else self.data.shape[0] - 1
|
|
751
847
|
ijmax = np.unravel_index(
|
|
752
|
-
np.argmax(self.data[imin:imax+1, jmin:jmax+1]),
|
|
753
|
-
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,
|
|
754
850
|
)
|
|
755
851
|
ixpix = ijmax[1] + jmin + 1
|
|
756
852
|
iypix = ijmax[0] + imin + 1
|