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
|
@@ -17,6 +17,7 @@ from tkinter import simpledialog
|
|
|
17
17
|
import sys
|
|
18
18
|
|
|
19
19
|
from astropy.io import fits
|
|
20
|
+
|
|
20
21
|
try:
|
|
21
22
|
from maskfill import maskfill
|
|
22
23
|
except ModuleNotFoundError as e:
|
|
@@ -34,10 +35,15 @@ import os
|
|
|
34
35
|
from pathlib import Path
|
|
35
36
|
from rich import print
|
|
36
37
|
|
|
38
|
+
from .askextension import ask_extension_input_image
|
|
37
39
|
from .centerchildparent import center_on_parent
|
|
38
40
|
from .definitions import lacosmic_default_dict
|
|
39
41
|
from .definitions import DEFAULT_NPOINTS_INTERP
|
|
40
42
|
from .definitions import DEFAULT_DEGREE_INTERP
|
|
43
|
+
from .definitions import DEFAULT_MASKFILL_SIZE
|
|
44
|
+
from .definitions import DEFAULT_MASKFILL_OPERATOR
|
|
45
|
+
from .definitions import DEFAULT_MASKFILL_SMOOTH
|
|
46
|
+
from .definitions import DEFAULT_MASKFILL_VERBOSE
|
|
41
47
|
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
42
48
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_X
|
|
43
49
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
|
|
@@ -52,12 +58,14 @@ from .interpolationeditor import InterpolationEditor
|
|
|
52
58
|
from .imagedisplay import ImageDisplay
|
|
53
59
|
from .lacosmicpad import lacosmicpad
|
|
54
60
|
from .mergemasks import merge_peak_tail_masks
|
|
61
|
+
from .modalprogressbar import ModalProgressBar
|
|
55
62
|
from .parametereditor import ParameterEditor
|
|
56
63
|
from .reviewcosmicray import ReviewCosmicRay
|
|
57
|
-
from .
|
|
64
|
+
from .trackedbutton import TrackedTkButton
|
|
58
65
|
|
|
59
66
|
from ..imshow import imshow
|
|
60
67
|
from ..sliceregion import SliceRegion2D
|
|
68
|
+
from ..version import VERSION
|
|
61
69
|
from ..zscale import zscale
|
|
62
70
|
|
|
63
71
|
import matplotlib
|
|
@@ -127,7 +135,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
127
135
|
Update the overlay of cosmic ray pixels on the image.
|
|
128
136
|
apply_lacosmic()
|
|
129
137
|
Apply the L.A.Cosmic algorithm to the data.
|
|
130
|
-
|
|
138
|
+
review_detected_cr()
|
|
131
139
|
Examine detected cosmic rays.
|
|
132
140
|
stop_app()
|
|
133
141
|
Stop the application.
|
|
@@ -182,10 +190,26 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
182
190
|
last_ymax : int
|
|
183
191
|
Last used maximum y-coordinate for region selection.
|
|
184
192
|
From 1 to NAXIS2.
|
|
193
|
+
last_inbkg : str or None
|
|
194
|
+
Last used input background image FITS file.
|
|
195
|
+
last_extnum_inbkg : int or None
|
|
196
|
+
Last used FITS extension number for the input background image.
|
|
197
|
+
last_invar : str or None
|
|
198
|
+
Last used input variance image FITS file.
|
|
199
|
+
last_extnum_invar : int or None
|
|
200
|
+
Last used FITS extension number for the input variance image.
|
|
185
201
|
last_npoints : int
|
|
186
202
|
Last used number of points for interpolation.
|
|
187
203
|
last_degree : int
|
|
188
204
|
Last used degree for interpolation.
|
|
205
|
+
last_maskfill_size : int
|
|
206
|
+
Last used size parameter for maskfill.
|
|
207
|
+
last_maskfill_operator : str
|
|
208
|
+
Last used operator parameter for maskfill.
|
|
209
|
+
last_maskfill_smooth : bool
|
|
210
|
+
Last used smooth parameter for maskfill.
|
|
211
|
+
last_maskfill_verbose : bool
|
|
212
|
+
Last used verbose parameter for maskfill.
|
|
189
213
|
cleandata_lacosmic : np.ndarray
|
|
190
214
|
The cleaned data returned from L.A.Cosmic.
|
|
191
215
|
cr_labels : np.ndarray
|
|
@@ -196,13 +220,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
196
220
|
Flag to indicate if the review window is active.
|
|
197
221
|
"""
|
|
198
222
|
self.root = root
|
|
199
|
-
# self.root.geometry("
|
|
223
|
+
# self.root.geometry("800x700+50+0") # This does not work in Fedora
|
|
200
224
|
self.width = width
|
|
201
225
|
self.height = height
|
|
202
226
|
self.verbose = verbose
|
|
203
227
|
self.root.minsize(self.width, self.height)
|
|
204
228
|
self.root.update_idletasks()
|
|
205
|
-
self.root.title("Cosmic Ray Cleaner")
|
|
229
|
+
self.root.title(f"Cosmic Ray Cleaner (TEA version {VERSION})")
|
|
206
230
|
self.fontfamily = fontfamily
|
|
207
231
|
self.fontsize = fontsize
|
|
208
232
|
self.default_font = tkfont.nametofont("TkDefaultFont")
|
|
@@ -225,8 +249,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
225
249
|
self.last_xmax = self.data.shape[1]
|
|
226
250
|
self.last_ymin = 1
|
|
227
251
|
self.last_ymax = self.data.shape[0]
|
|
252
|
+
self.last_inbkg = None
|
|
253
|
+
self.last_extnum_inbkg = None
|
|
254
|
+
self.inbkg_data = None
|
|
255
|
+
self.last_invar = None
|
|
256
|
+
self.last_extnum_invar = None
|
|
257
|
+
self.invar_data = None
|
|
228
258
|
self.last_npoints = DEFAULT_NPOINTS_INTERP
|
|
229
259
|
self.last_degree = DEFAULT_DEGREE_INTERP
|
|
260
|
+
self.last_maskfill_size = DEFAULT_MASKFILL_SIZE
|
|
261
|
+
self.last_maskfill_operator = DEFAULT_MASKFILL_OPERATOR
|
|
262
|
+
self.last_maskfill_smooth = DEFAULT_MASKFILL_SMOOTH
|
|
263
|
+
self.last_maskfill_verbose = DEFAULT_MASKFILL_VERBOSE
|
|
230
264
|
self.create_widgets()
|
|
231
265
|
self.cleandata_lacosmic = None
|
|
232
266
|
self.cr_labels = None
|
|
@@ -235,7 +269,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
235
269
|
|
|
236
270
|
def process_detected_cr(self, dilation):
|
|
237
271
|
"""Process the detected cosmic ray mask.
|
|
238
|
-
|
|
272
|
+
|
|
239
273
|
Parameters
|
|
240
274
|
----------
|
|
241
275
|
dilation : int
|
|
@@ -246,20 +280,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
246
280
|
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
247
281
|
if dilation > 0:
|
|
248
282
|
# Dilate the mask by the specified number of pixels
|
|
249
|
-
self.mask_crfound = dilatemask(
|
|
250
|
-
mask=self.mask_crfound, iterations=dilation, connectivity=1
|
|
251
|
-
)
|
|
283
|
+
self.mask_crfound = dilatemask(mask=self.mask_crfound, iterations=dilation, connectivity=1)
|
|
252
284
|
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
253
285
|
sdum = str(num_cr_pixels_after_dilation)
|
|
254
286
|
else:
|
|
255
287
|
sdum = str(num_cr_pixels_before_dilation)
|
|
256
|
-
print(
|
|
257
|
-
"Number of cosmic ray pixels detected..........: "
|
|
258
|
-
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
259
|
-
)
|
|
260
288
|
if dilation > 0:
|
|
261
289
|
print(
|
|
262
|
-
|
|
290
|
+
"Number of cosmic ray pixels before dilation.......: "
|
|
291
|
+
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
292
|
+
)
|
|
293
|
+
print(
|
|
294
|
+
f"Number of cosmic ray pixels after dilation........: "
|
|
263
295
|
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
264
296
|
)
|
|
265
297
|
# Label connected components in the mask; note that by default,
|
|
@@ -267,9 +299,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
267
299
|
# diagonal connections too, so we define a 3x3 square.
|
|
268
300
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
269
301
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
270
|
-
print(f"Number of cosmic ray features (grouped pixels)
|
|
302
|
+
print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
|
|
271
303
|
self.replace_detected_cr_button.config(state=tk.NORMAL)
|
|
272
|
-
self.
|
|
304
|
+
self.review_detected_cr_button.config(state=tk.NORMAL)
|
|
273
305
|
self.update_cr_overlay()
|
|
274
306
|
self.use_cursor = True
|
|
275
307
|
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
@@ -278,10 +310,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
278
310
|
self.cr_labels = None
|
|
279
311
|
self.num_features = 0
|
|
280
312
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
281
|
-
self.
|
|
313
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
282
314
|
|
|
283
315
|
def load_detected_cr_from_file(self):
|
|
284
316
|
"""Load detected cosmic ray mask from a FITS file."""
|
|
317
|
+
if np.any(self.mask_crfound):
|
|
318
|
+
overwrite = messagebox.askyesno(
|
|
319
|
+
"Overwrite Cosmic Ray Mask",
|
|
320
|
+
"A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
|
|
321
|
+
)
|
|
322
|
+
if not overwrite:
|
|
323
|
+
return
|
|
285
324
|
crmask_file = filedialog.askopenfilename(
|
|
286
325
|
initialdir=os.getcwd(),
|
|
287
326
|
title="Select FITS file with cosmic ray mask",
|
|
@@ -289,18 +328,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
289
328
|
)
|
|
290
329
|
if crmask_file:
|
|
291
330
|
print(f"Selected input FITS file: {crmask_file}")
|
|
292
|
-
extension =
|
|
293
|
-
"Select Extension",
|
|
294
|
-
f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
|
|
295
|
-
initialvalue=None,
|
|
296
|
-
)
|
|
297
|
-
try:
|
|
298
|
-
extension = int(extension)
|
|
299
|
-
except ValueError:
|
|
300
|
-
pass # Keep as string
|
|
301
|
-
dilation = simpledialog.askinteger(
|
|
302
|
-
"Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
|
|
303
|
-
)
|
|
331
|
+
extension = ask_extension_input_image(crmask_file, self.data.shape)
|
|
304
332
|
try:
|
|
305
333
|
with fits.open(crmask_file, mode="readonly") as hdul:
|
|
306
334
|
if isinstance(extension, int):
|
|
@@ -309,6 +337,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
309
337
|
else:
|
|
310
338
|
if extension not in hdul:
|
|
311
339
|
raise KeyError(f"Extension name '{extension}' not found.")
|
|
340
|
+
if hdul[extension].header["BITPIX"] not in [8, 16]:
|
|
341
|
+
raise ValueError("Cosmic ray mask must be of integer type (BITPIX=8 or 16).")
|
|
312
342
|
mask_crfound_loaded = hdul[extension].data.astype(bool)
|
|
313
343
|
if mask_crfound_loaded.shape != self.data.shape:
|
|
314
344
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -316,9 +346,14 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
316
346
|
raise ValueError("Cosmic ray mask has different shape.")
|
|
317
347
|
self.mask_crfound = mask_crfound_loaded
|
|
318
348
|
print(f"Loaded cosmic ray mask from {crmask_file}")
|
|
349
|
+
dilation = simpledialog.askinteger(
|
|
350
|
+
"Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
|
|
351
|
+
)
|
|
319
352
|
self.process_detected_cr(dilation=dilation)
|
|
353
|
+
# self.lacosmic_params["dilation"]["value"] = dilation
|
|
354
|
+
self.cleandata_lacosmic = None # Invalidate previous L.A.Cosmic cleaned data
|
|
320
355
|
except Exception as e:
|
|
321
|
-
|
|
356
|
+
messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
|
|
322
357
|
|
|
323
358
|
def load_fits_file(self):
|
|
324
359
|
"""Load the FITS file and auxiliary file (if provided).
|
|
@@ -378,7 +413,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
378
413
|
else:
|
|
379
414
|
if self.extension_auxfile not in hdul_aux:
|
|
380
415
|
raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
|
|
381
|
-
print(
|
|
416
|
+
print(
|
|
417
|
+
f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}"
|
|
418
|
+
)
|
|
382
419
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
383
420
|
if self.auxdata.shape != self.data.shape:
|
|
384
421
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -387,6 +424,43 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
387
424
|
except Exception as e:
|
|
388
425
|
sys.exit(f"Error loading auxiliary FITS file: {e}")
|
|
389
426
|
|
|
427
|
+
def load_auxdata_from_file(self):
|
|
428
|
+
"""Load auxiliary data from a FITS file."""
|
|
429
|
+
if self.auxfile is not None:
|
|
430
|
+
overwrite = messagebox.askyesno(
|
|
431
|
+
"Overwrite Auxiliary Data",
|
|
432
|
+
f"An auxiliary file is already loaded:\n\n{self.auxfile}\n\n" "Do you want to overwrite it?",
|
|
433
|
+
)
|
|
434
|
+
if not overwrite:
|
|
435
|
+
return
|
|
436
|
+
auxfile = filedialog.askopenfilename(
|
|
437
|
+
initialdir=os.getcwd(),
|
|
438
|
+
title="Select auxiliary FITS file",
|
|
439
|
+
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
440
|
+
)
|
|
441
|
+
if auxfile:
|
|
442
|
+
extension = ask_extension_input_image(auxfile, self.data.shape)
|
|
443
|
+
try:
|
|
444
|
+
with fits.open(auxfile, mode="readonly") as hdul:
|
|
445
|
+
if isinstance(extension, int):
|
|
446
|
+
if extension < 0 or extension >= len(hdul):
|
|
447
|
+
raise IndexError(f"Extension index {extension} out of range.")
|
|
448
|
+
else:
|
|
449
|
+
if extension not in hdul:
|
|
450
|
+
raise KeyError(f"Extension name '{extension}' not found.")
|
|
451
|
+
auxdata_loaded = hdul[extension].data
|
|
452
|
+
if auxdata_loaded.shape != self.data.shape:
|
|
453
|
+
print(f"data shape...: {self.data.shape}")
|
|
454
|
+
print(f"auxdata shape: {auxdata_loaded.shape}")
|
|
455
|
+
raise ValueError("Auxiliary file has different shape.")
|
|
456
|
+
self.auxfile = auxfile
|
|
457
|
+
self.auxdata = auxdata_loaded
|
|
458
|
+
self.extension_auxfile = extension
|
|
459
|
+
print(f"Loaded auxiliary data from {auxfile}")
|
|
460
|
+
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
461
|
+
except Exception as e:
|
|
462
|
+
print(f"Error loading auxiliary FITS file: {e}")
|
|
463
|
+
|
|
390
464
|
def save_fits_file(self):
|
|
391
465
|
"""Save the cleaned FITS file.
|
|
392
466
|
|
|
@@ -450,74 +524,153 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
450
524
|
and canvas for image display, along with the toolbar for navigation.
|
|
451
525
|
The relevant attributes are stored in the instance for later use.
|
|
452
526
|
"""
|
|
527
|
+
# Define instance of TrackedTkButton, that facilitates to show help information
|
|
528
|
+
# for each button displayed in the current application window.
|
|
529
|
+
tkbutton = TrackedTkButton(self.root)
|
|
530
|
+
|
|
453
531
|
# Row 1 of buttons
|
|
454
532
|
self.button_frame1 = tk.Frame(self.root)
|
|
455
533
|
self.button_frame1.pack(pady=5)
|
|
456
|
-
self.run_lacosmic_button =
|
|
534
|
+
self.run_lacosmic_button = tkbutton.new(
|
|
535
|
+
self.button_frame1,
|
|
536
|
+
text="Run L.A.Cosmic",
|
|
537
|
+
command=self.run_lacosmic,
|
|
538
|
+
help_text="Run the L.A.Cosmic algorithm to detect cosmic rays in the image.",
|
|
539
|
+
)
|
|
457
540
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
458
|
-
self.
|
|
459
|
-
self.button_frame1,
|
|
541
|
+
self.load_auxdata_button = tkbutton.new(
|
|
542
|
+
self.button_frame1,
|
|
543
|
+
text="Load auxdata",
|
|
544
|
+
command=self.load_auxdata_from_file,
|
|
545
|
+
help_text="Load an auxiliary FITS file for display.",
|
|
546
|
+
)
|
|
547
|
+
self.load_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
548
|
+
self.load_detected_cr_button = tkbutton.new(
|
|
549
|
+
self.button_frame1,
|
|
550
|
+
text="Load CR mask",
|
|
551
|
+
command=self.load_detected_cr_from_file,
|
|
552
|
+
help_text="Load a previously saved cosmic ray mask from a FITS file.",
|
|
460
553
|
)
|
|
461
554
|
self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
462
|
-
self.replace_detected_cr_button =
|
|
463
|
-
self.button_frame1,
|
|
555
|
+
self.replace_detected_cr_button = tkbutton.new(
|
|
556
|
+
self.button_frame1,
|
|
557
|
+
text="Replace detected CRs",
|
|
558
|
+
command=self.apply_lacosmic,
|
|
559
|
+
help_text="Apply the cleaning to the detected cosmic rays.",
|
|
464
560
|
)
|
|
465
561
|
self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
466
562
|
self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
467
|
-
self.
|
|
468
|
-
self.button_frame1,
|
|
563
|
+
self.review_detected_cr_button = tkbutton.new(
|
|
564
|
+
self.button_frame1,
|
|
565
|
+
text="Review detected CRs",
|
|
566
|
+
command=lambda: self.review_detected_cr(1),
|
|
567
|
+
help_text="Review the detected cosmic rays.",
|
|
469
568
|
)
|
|
470
|
-
self.
|
|
471
|
-
self.
|
|
569
|
+
self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
570
|
+
self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
472
571
|
|
|
473
572
|
# Row 2 of buttons
|
|
474
573
|
self.button_frame2 = tk.Frame(self.root)
|
|
475
574
|
self.button_frame2.pack(pady=5)
|
|
476
|
-
self.
|
|
575
|
+
self.use_cursor = False
|
|
576
|
+
self.use_cursor_button = tkbutton.new(
|
|
577
|
+
self.button_frame2,
|
|
578
|
+
text="[c]ursor: OFF",
|
|
579
|
+
command=self.set_cursor_onoff,
|
|
580
|
+
help_text="Toggle the cursor ON or OFF (to select CR with mouse).",
|
|
581
|
+
alttext="[c]ursor: ??",
|
|
582
|
+
)
|
|
583
|
+
self.use_cursor_button.pack(side=tk.LEFT, padx=5)
|
|
584
|
+
self.toggle_auxdata_button = tkbutton.new(
|
|
585
|
+
self.button_frame2,
|
|
586
|
+
text="[t]oggle data",
|
|
587
|
+
command=self.toggle_auxdata,
|
|
588
|
+
help_text="Toggle the display of auxiliary data.",
|
|
589
|
+
)
|
|
477
590
|
self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
478
591
|
if self.auxdata is None:
|
|
479
592
|
self.toggle_auxdata_button.config(state=tk.DISABLED)
|
|
480
593
|
else:
|
|
481
594
|
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
482
595
|
self.image_aspect = "equal"
|
|
483
|
-
self.toggle_aspect_button =
|
|
484
|
-
self.button_frame2,
|
|
596
|
+
self.toggle_aspect_button = tkbutton.new(
|
|
597
|
+
self.button_frame2,
|
|
598
|
+
text=f"[a]spect: {self.image_aspect}",
|
|
599
|
+
command=self.toggle_aspect,
|
|
600
|
+
help_text="Toggle the image aspect ratio.",
|
|
485
601
|
)
|
|
486
602
|
self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
|
|
487
|
-
self.save_button =
|
|
603
|
+
self.save_button = tkbutton.new(
|
|
604
|
+
self.button_frame2,
|
|
605
|
+
text="Save cleaned FITS",
|
|
606
|
+
command=self.save_fits_file,
|
|
607
|
+
help_text="Save the cleaned FITS file.",
|
|
608
|
+
)
|
|
488
609
|
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
489
610
|
self.save_button.config(state=tk.DISABLED) # Initially disabled
|
|
490
|
-
self.stop_button =
|
|
611
|
+
self.stop_button = tkbutton.new(
|
|
612
|
+
self.button_frame2, text="Stop program", command=self.stop_app, help_text="Stop the application."
|
|
613
|
+
)
|
|
491
614
|
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
492
615
|
|
|
493
616
|
# Row 3 of buttons
|
|
494
617
|
self.button_frame3 = tk.Frame(self.root)
|
|
495
618
|
self.button_frame3.pack(pady=5)
|
|
496
|
-
self.use_cursor = False
|
|
497
|
-
self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
|
|
498
|
-
self.use_cursor_button.pack(side=tk.LEFT, padx=5)
|
|
499
619
|
vmin, vmax = zscale(self.data)
|
|
500
|
-
self.vmin_button =
|
|
620
|
+
self.vmin_button = tkbutton.new(
|
|
621
|
+
self.button_frame3,
|
|
622
|
+
text=f"vmin: {vmin:.2f}",
|
|
623
|
+
command=self.set_vmin,
|
|
624
|
+
help_text="Set the minimum value for the display scale.",
|
|
625
|
+
alttext="vmin: ??",
|
|
626
|
+
)
|
|
501
627
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
502
|
-
self.vmax_button =
|
|
628
|
+
self.vmax_button = tkbutton.new(
|
|
629
|
+
self.button_frame3,
|
|
630
|
+
text=f"vmax: {vmax:.2f}",
|
|
631
|
+
command=self.set_vmax,
|
|
632
|
+
help_text="Set the maximum value for the display scale.",
|
|
633
|
+
alttext="vmax: ??",
|
|
634
|
+
)
|
|
503
635
|
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
504
|
-
self.set_minmax_button =
|
|
636
|
+
self.set_minmax_button = tkbutton.new(
|
|
637
|
+
self.button_frame3,
|
|
638
|
+
text="minmax [,]",
|
|
639
|
+
command=self.set_minmax,
|
|
640
|
+
help_text="Set the minimum and maximum values for the display scale.",
|
|
641
|
+
)
|
|
505
642
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
506
|
-
self.set_zscale_button =
|
|
643
|
+
self.set_zscale_button = tkbutton.new(
|
|
644
|
+
self.button_frame3,
|
|
645
|
+
text="zscale [/]",
|
|
646
|
+
command=self.set_zscale,
|
|
647
|
+
help_text="Set the display scale using zscale.",
|
|
648
|
+
)
|
|
507
649
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
508
650
|
if self.overplot_cr_pixels:
|
|
509
|
-
self.overplot_cr_button =
|
|
651
|
+
self.overplot_cr_button = tkbutton.new(
|
|
510
652
|
self.button_frame3,
|
|
511
653
|
text="CR overlay: ON ",
|
|
512
654
|
command=self.toggle_cr_overlay,
|
|
655
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
656
|
+
alttext="CR overlay: ??",
|
|
513
657
|
)
|
|
514
658
|
else:
|
|
515
|
-
self.overplot_cr_button =
|
|
659
|
+
self.overplot_cr_button = tkbutton.new(
|
|
516
660
|
self.button_frame3,
|
|
517
661
|
text="CR overlay: OFF",
|
|
518
662
|
command=self.toggle_cr_overlay,
|
|
663
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
664
|
+
alttext="CR overlay: ??",
|
|
519
665
|
)
|
|
520
666
|
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
667
|
+
self.help_button = tkbutton.new(
|
|
668
|
+
self.button_frame3,
|
|
669
|
+
text="Help",
|
|
670
|
+
command=tkbutton.show_help,
|
|
671
|
+
help_text="Show help information for all buttons.",
|
|
672
|
+
)
|
|
673
|
+
self.help_button.pack(side=tk.LEFT, padx=5)
|
|
521
674
|
|
|
522
675
|
# Figure
|
|
523
676
|
self.plot_frame = tk.Frame(self.root)
|
|
@@ -606,6 +759,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
606
759
|
|
|
607
760
|
def run_lacosmic(self):
|
|
608
761
|
"""Run L.A.Cosmic to detect cosmic rays."""
|
|
762
|
+
if np.any(self.mask_crfound):
|
|
763
|
+
overwrite = messagebox.askyesno(
|
|
764
|
+
"Overwrite Cosmic Ray Mask",
|
|
765
|
+
"A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
|
|
766
|
+
)
|
|
767
|
+
if not overwrite:
|
|
768
|
+
return
|
|
609
769
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
610
770
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
611
771
|
editor_window = tk.Toplevel(self.root)
|
|
@@ -619,6 +779,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
619
779
|
ymin=self.last_ymin,
|
|
620
780
|
ymax=self.last_ymax,
|
|
621
781
|
imgshape=self.data.shape,
|
|
782
|
+
inbkg=self.last_inbkg,
|
|
783
|
+
extnum_inbkg=self.last_extnum_inbkg,
|
|
784
|
+
invar=self.last_invar,
|
|
785
|
+
extnum_invar=self.last_extnum_invar,
|
|
622
786
|
)
|
|
623
787
|
# Make it modal (blocks interaction with main window)
|
|
624
788
|
editor_window.transient(self.root)
|
|
@@ -633,6 +797,24 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
633
797
|
self.last_xmax = updated_params["xmax"]["value"]
|
|
634
798
|
self.last_ymin = updated_params["ymin"]["value"]
|
|
635
799
|
self.last_ymax = updated_params["ymax"]["value"]
|
|
800
|
+
self.last_inbkg = updated_params["inbkg"]["value"]
|
|
801
|
+
self.last_extnum_inbkg = updated_params["extnum_inbkg"]["value"]
|
|
802
|
+
self.last_invar = updated_params["invar"]["value"]
|
|
803
|
+
self.last_extnum_invar = updated_params["extnum_invar"]["value"]
|
|
804
|
+
if self.last_inbkg is not None:
|
|
805
|
+
with fits.open(self.last_inbkg, mode="readonly") as hdul_inbkg:
|
|
806
|
+
if self.last_extnum_inbkg < 0 or self.last_extnum_inbkg >= len(hdul_inbkg):
|
|
807
|
+
raise IndexError(f"Extension index {self.last_extnum_inbkg} out of range.")
|
|
808
|
+
self.inbkg_data = hdul_inbkg[self.last_extnum_inbkg].data.astype(np.float32)
|
|
809
|
+
else:
|
|
810
|
+
self.inbkg_data = None
|
|
811
|
+
if self.last_invar is not None:
|
|
812
|
+
with fits.open(self.last_invar, mode="readonly") as hdul_invar:
|
|
813
|
+
if self.last_extnum_invar < 0 or self.last_extnum_invar >= len(hdul_invar):
|
|
814
|
+
raise IndexError(f"Extension index {self.last_extnum_invar} out of range.")
|
|
815
|
+
self.invar_data = hdul_invar[self.last_extnum_invar].data.astype(np.float32)
|
|
816
|
+
else:
|
|
817
|
+
self.invar_data = None
|
|
636
818
|
usefulregion = SliceRegion2D(
|
|
637
819
|
f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
|
|
638
820
|
).python
|
|
@@ -651,14 +833,27 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
651
833
|
borderpadd = updated_params["borderpadd"]["value"]
|
|
652
834
|
cleandata_lacosmic, mask_crfound = lacosmicpad(
|
|
653
835
|
pad_width=borderpadd,
|
|
836
|
+
show_arguments=self.verbose,
|
|
654
837
|
ccd=self.data,
|
|
655
|
-
|
|
656
|
-
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
838
|
+
gain_apply=True, # Always apply gain
|
|
657
839
|
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
658
840
|
sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
|
|
659
841
|
objlim=self.lacosmic_params["run1_objlim"]["value"],
|
|
842
|
+
gain=self.lacosmic_params["run1_gain"]["value"],
|
|
843
|
+
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
844
|
+
satlevel=self.lacosmic_params["run1_satlevel"]["value"],
|
|
660
845
|
niter=self.lacosmic_params["run1_niter"]["value"],
|
|
846
|
+
sepmed=self.lacosmic_params["run1_sepmed"]["value"],
|
|
847
|
+
cleantype=self.lacosmic_params["run1_cleantype"]["value"],
|
|
848
|
+
fsmode=self.lacosmic_params["run1_fsmode"]["value"],
|
|
849
|
+
psfmodel=self.lacosmic_params["run1_psfmodel"]["value"],
|
|
850
|
+
psffwhm_x=self.lacosmic_params["run1_psffwhm_x"]["value"],
|
|
851
|
+
psffwhm_y=self.lacosmic_params["run1_psffwhm_y"]["value"],
|
|
852
|
+
psfsize=self.lacosmic_params["run1_psfsize"]["value"],
|
|
853
|
+
psfbeta=self.lacosmic_params["run1_psfbeta"]["value"],
|
|
661
854
|
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
855
|
+
inbkg=self.inbkg_data,
|
|
856
|
+
invar=self.invar_data,
|
|
662
857
|
)
|
|
663
858
|
# Apply usefulmask to consider only selected region
|
|
664
859
|
cleandata_lacosmic *= usefulmask
|
|
@@ -668,24 +863,34 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
668
863
|
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
669
864
|
cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
|
|
670
865
|
pad_width=borderpadd,
|
|
866
|
+
show_arguments=self.verbose,
|
|
671
867
|
ccd=self.data,
|
|
672
|
-
|
|
673
|
-
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
868
|
+
gain_apply=True, # Always apply gain
|
|
674
869
|
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
675
870
|
sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
|
|
676
871
|
objlim=self.lacosmic_params["run2_objlim"]["value"],
|
|
872
|
+
gain=self.lacosmic_params["run2_gain"]["value"],
|
|
873
|
+
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
874
|
+
satlevel=self.lacosmic_params["run2_satlevel"]["value"],
|
|
677
875
|
niter=self.lacosmic_params["run2_niter"]["value"],
|
|
876
|
+
sepmed=self.lacosmic_params["run2_sepmed"]["value"],
|
|
877
|
+
cleantype=self.lacosmic_params["run2_cleantype"]["value"],
|
|
878
|
+
fsmode=self.lacosmic_params["run2_fsmode"]["value"],
|
|
879
|
+
psfmodel=self.lacosmic_params["run2_psfmodel"]["value"],
|
|
880
|
+
psffwhm_x=self.lacosmic_params["run2_psffwhm_x"]["value"],
|
|
881
|
+
psffwhm_y=self.lacosmic_params["run2_psffwhm_y"]["value"],
|
|
882
|
+
psfsize=self.lacosmic_params["run2_psfsize"]["value"],
|
|
883
|
+
psfbeta=self.lacosmic_params["run2_psfbeta"]["value"],
|
|
678
884
|
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
885
|
+
inbkg=self.inbkg_data,
|
|
886
|
+
invar=self.invar_data,
|
|
679
887
|
)
|
|
680
888
|
# Apply usefulmask to consider only selected region
|
|
681
889
|
cleandata_lacosmic2 *= usefulmask
|
|
682
890
|
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
683
891
|
# Combine results from both runs
|
|
684
892
|
if np.any(mask_crfound):
|
|
685
|
-
|
|
686
|
-
print(f"Number of cosmic ray pixels (run2).......: {np.sum(mask_crfound2)}")
|
|
687
|
-
mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2)
|
|
688
|
-
print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
|
|
893
|
+
mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2, verbose=True)
|
|
689
894
|
# Use the cleandata from the second run
|
|
690
895
|
cleandata_lacosmic = cleandata_lacosmic2
|
|
691
896
|
# Select the image region to process
|
|
@@ -732,9 +937,6 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
732
937
|
# recalculate labels and number of features
|
|
733
938
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
734
939
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
735
|
-
sdum = str(np.sum(self.mask_crfound))
|
|
736
|
-
print(f"Number of cosmic ray pixels detected by L.A.Cosmic...........: {sdum}")
|
|
737
|
-
print(f"Number of cosmic rays (grouped pixels) detected by L.A.Cosmic: {self.num_features:>{len(sdum)}}")
|
|
738
940
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
739
941
|
editor_window = tk.Toplevel(self.root)
|
|
740
942
|
center_on_parent(child=editor_window, parent=self.root)
|
|
@@ -743,7 +945,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
743
945
|
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
744
946
|
last_npoints=self.last_npoints,
|
|
745
947
|
last_degree=self.last_degree,
|
|
948
|
+
last_maskfill_size=self.last_maskfill_size,
|
|
949
|
+
last_maskfill_operator=self.last_maskfill_operator,
|
|
950
|
+
last_maskfill_smooth=self.last_maskfill_smooth,
|
|
951
|
+
last_maskfill_verbose=self.last_maskfill_verbose,
|
|
746
952
|
auxdata=self.auxdata,
|
|
953
|
+
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
747
954
|
xmin=self.last_xmin,
|
|
748
955
|
xmax=self.last_xmax,
|
|
749
956
|
ymin=self.last_ymin,
|
|
@@ -760,8 +967,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
760
967
|
if cleaning_method is None:
|
|
761
968
|
print("Interpolation method selection cancelled. No cleaning applied!")
|
|
762
969
|
return
|
|
970
|
+
# Update last employed parameters
|
|
763
971
|
self.last_npoints = editor.npoints
|
|
764
972
|
self.last_degree = editor.degree
|
|
973
|
+
self.last_maskfill_size = editor.maskfill_size
|
|
974
|
+
self.last_maskfill_operator = editor.maskfill_operator
|
|
975
|
+
self.last_maskfill_smooth = editor.maskfill_smooth
|
|
976
|
+
self.last_maskfill_verbose = editor.maskfill_verbose
|
|
765
977
|
cleaning_region = SliceRegion2D(
|
|
766
978
|
f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
|
|
767
979
|
).python
|
|
@@ -783,12 +995,19 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
783
995
|
data_has_been_modified = True
|
|
784
996
|
elif cleaning_method == "maskfill":
|
|
785
997
|
# Replace detected CR pixels with local median values
|
|
998
|
+
print(
|
|
999
|
+
f"Maskfill parameters: size={self.last_maskfill_size}, "
|
|
1000
|
+
f"operator={self.last_maskfill_operator}, "
|
|
1001
|
+
f"smooth={self.last_maskfill_smooth}, "
|
|
1002
|
+
f"verbose={self.last_maskfill_verbose}"
|
|
1003
|
+
)
|
|
786
1004
|
smoothed_output, _ = maskfill(
|
|
787
1005
|
input_image=self.data,
|
|
788
1006
|
mask=mask_crfound_region,
|
|
789
|
-
size=
|
|
790
|
-
operator=
|
|
791
|
-
smooth=
|
|
1007
|
+
size=self.last_maskfill_size,
|
|
1008
|
+
operator=self.last_maskfill_operator,
|
|
1009
|
+
smooth=self.last_maskfill_smooth,
|
|
1010
|
+
verbose=self.last_maskfill_verbose,
|
|
792
1011
|
)
|
|
793
1012
|
self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
|
|
794
1013
|
# update mask_fixed to include the newly fixed pixels
|
|
@@ -891,11 +1110,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
891
1110
|
if data_has_been_modified:
|
|
892
1111
|
self.save_button.config(state=tk.NORMAL)
|
|
893
1112
|
if self.num_features == 0:
|
|
894
|
-
self.
|
|
1113
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
895
1114
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
896
1115
|
self.update_cr_overlay()
|
|
897
1116
|
|
|
898
|
-
def
|
|
1117
|
+
def review_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
|
|
899
1118
|
"""Open a window to examine and possibly clean detected cosmic rays."""
|
|
900
1119
|
self.working_in_review_window = True
|
|
901
1120
|
review_window = tk.Toplevel(self.root)
|
|
@@ -906,6 +1125,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
906
1125
|
tmp_cr_labels[iypix - 1, ixpix - 1] = 1
|
|
907
1126
|
review = ReviewCosmicRay(
|
|
908
1127
|
root=review_window,
|
|
1128
|
+
root_width=self.width,
|
|
1129
|
+
root_height=self.height,
|
|
909
1130
|
data=self.data,
|
|
910
1131
|
auxdata=self.auxdata,
|
|
911
1132
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -920,6 +1141,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
920
1141
|
else:
|
|
921
1142
|
review = ReviewCosmicRay(
|
|
922
1143
|
root=review_window,
|
|
1144
|
+
root_width=self.width,
|
|
1145
|
+
root_height=self.height,
|
|
923
1146
|
data=self.data,
|
|
924
1147
|
auxdata=self.auxdata,
|
|
925
1148
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -961,7 +1184,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
961
1184
|
if review.num_cr_cleaned > 0:
|
|
962
1185
|
self.save_button.config(state=tk.NORMAL)
|
|
963
1186
|
if self.num_features == 0:
|
|
964
|
-
self.
|
|
1187
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
965
1188
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
966
1189
|
self.update_cr_overlay()
|
|
967
1190
|
|
|
@@ -1069,4 +1292,4 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
1069
1292
|
else:
|
|
1070
1293
|
ixpix = None
|
|
1071
1294
|
iypix = None
|
|
1072
|
-
self.
|
|
1295
|
+
self.review_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)
|