teareduce 0.5.9__py3-none-any.whl → 0.6.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- teareduce/cleanest/__init__.py +2 -2
- teareduce/cleanest/__main__.py +11 -5
- teareduce/cleanest/askextension.py +85 -0
- teareduce/cleanest/cosmicraycleanerapp.py +289 -64
- teareduce/cleanest/definitions.py +55 -9
- teareduce/cleanest/gausskernel2d_elliptical.py +54 -0
- teareduce/cleanest/interpolate.py +65 -48
- teareduce/cleanest/interpolationeditor.py +196 -52
- teareduce/cleanest/lacosmicpad.py +92 -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.0.dist-info}/METADATA +1 -1
- {teareduce-0.5.9.dist-info → teareduce-0.6.0.dist-info}/RECORD +19 -16
- {teareduce-0.5.9.dist-info → teareduce-0.6.0.dist-info}/WHEEL +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.0.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.9.dist-info → teareduce-0.6.0.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,21 +280,15 @@ 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
|
-
)
|
|
288
|
+
print("Number of cosmic ray pixels detected..........: " f"{num_cr_pixels_before_dilation:>{len(sdum)}}")
|
|
260
289
|
if dilation > 0:
|
|
261
290
|
print(
|
|
262
|
-
f"Number of cosmic ray pixels after dilation....: "
|
|
263
|
-
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
291
|
+
f"Number of cosmic ray pixels after dilation....: " f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
264
292
|
)
|
|
265
293
|
# Label connected components in the mask; note that by default,
|
|
266
294
|
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
@@ -269,7 +297,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
269
297
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
270
298
|
print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
271
299
|
self.replace_detected_cr_button.config(state=tk.NORMAL)
|
|
272
|
-
self.
|
|
300
|
+
self.review_detected_cr_button.config(state=tk.NORMAL)
|
|
273
301
|
self.update_cr_overlay()
|
|
274
302
|
self.use_cursor = True
|
|
275
303
|
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
@@ -278,10 +306,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
278
306
|
self.cr_labels = None
|
|
279
307
|
self.num_features = 0
|
|
280
308
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
281
|
-
self.
|
|
309
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
282
310
|
|
|
283
311
|
def load_detected_cr_from_file(self):
|
|
284
312
|
"""Load detected cosmic ray mask from a FITS file."""
|
|
313
|
+
if np.any(self.mask_crfound):
|
|
314
|
+
overwrite = messagebox.askyesno(
|
|
315
|
+
"Overwrite Cosmic Ray Mask",
|
|
316
|
+
"A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
|
|
317
|
+
)
|
|
318
|
+
if not overwrite:
|
|
319
|
+
return
|
|
285
320
|
crmask_file = filedialog.askopenfilename(
|
|
286
321
|
initialdir=os.getcwd(),
|
|
287
322
|
title="Select FITS file with cosmic ray mask",
|
|
@@ -289,18 +324,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
289
324
|
)
|
|
290
325
|
if crmask_file:
|
|
291
326
|
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
|
-
)
|
|
327
|
+
extension = ask_extension_input_image(crmask_file, self.data.shape)
|
|
304
328
|
try:
|
|
305
329
|
with fits.open(crmask_file, mode="readonly") as hdul:
|
|
306
330
|
if isinstance(extension, int):
|
|
@@ -309,6 +333,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
309
333
|
else:
|
|
310
334
|
if extension not in hdul:
|
|
311
335
|
raise KeyError(f"Extension name '{extension}' not found.")
|
|
336
|
+
if hdul[extension].header["BITPIX"] not in [8, 16]:
|
|
337
|
+
raise ValueError("Cosmic ray mask must be of integer type (BITPIX=8 or 16).")
|
|
312
338
|
mask_crfound_loaded = hdul[extension].data.astype(bool)
|
|
313
339
|
if mask_crfound_loaded.shape != self.data.shape:
|
|
314
340
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -316,9 +342,14 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
316
342
|
raise ValueError("Cosmic ray mask has different shape.")
|
|
317
343
|
self.mask_crfound = mask_crfound_loaded
|
|
318
344
|
print(f"Loaded cosmic ray mask from {crmask_file}")
|
|
345
|
+
dilation = simpledialog.askinteger(
|
|
346
|
+
"Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
|
|
347
|
+
)
|
|
319
348
|
self.process_detected_cr(dilation=dilation)
|
|
349
|
+
# self.lacosmic_params["dilation"]["value"] = dilation
|
|
350
|
+
self.cleandata_lacosmic = None # Invalidate previous L.A.Cosmic cleaned data
|
|
320
351
|
except Exception as e:
|
|
321
|
-
|
|
352
|
+
messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
|
|
322
353
|
|
|
323
354
|
def load_fits_file(self):
|
|
324
355
|
"""Load the FITS file and auxiliary file (if provided).
|
|
@@ -378,7 +409,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
378
409
|
else:
|
|
379
410
|
if self.extension_auxfile not in hdul_aux:
|
|
380
411
|
raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
|
|
381
|
-
print(
|
|
412
|
+
print(
|
|
413
|
+
f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}"
|
|
414
|
+
)
|
|
382
415
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
383
416
|
if self.auxdata.shape != self.data.shape:
|
|
384
417
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -387,6 +420,43 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
387
420
|
except Exception as e:
|
|
388
421
|
sys.exit(f"Error loading auxiliary FITS file: {e}")
|
|
389
422
|
|
|
423
|
+
def load_auxdata_from_file(self):
|
|
424
|
+
"""Load auxiliary data from a FITS file."""
|
|
425
|
+
if self.auxfile is not None:
|
|
426
|
+
overwrite = messagebox.askyesno(
|
|
427
|
+
"Overwrite Auxiliary Data",
|
|
428
|
+
f"An auxiliary file is already loaded:\n\n{self.auxfile}\n\n" "Do you want to overwrite it?",
|
|
429
|
+
)
|
|
430
|
+
if not overwrite:
|
|
431
|
+
return
|
|
432
|
+
auxfile = filedialog.askopenfilename(
|
|
433
|
+
initialdir=os.getcwd(),
|
|
434
|
+
title="Select auxiliary FITS file",
|
|
435
|
+
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
436
|
+
)
|
|
437
|
+
if auxfile:
|
|
438
|
+
extension = ask_extension_input_image(auxfile, self.data.shape)
|
|
439
|
+
try:
|
|
440
|
+
with fits.open(auxfile, mode="readonly") as hdul:
|
|
441
|
+
if isinstance(extension, int):
|
|
442
|
+
if extension < 0 or extension >= len(hdul):
|
|
443
|
+
raise IndexError(f"Extension index {extension} out of range.")
|
|
444
|
+
else:
|
|
445
|
+
if extension not in hdul:
|
|
446
|
+
raise KeyError(f"Extension name '{extension}' not found.")
|
|
447
|
+
auxdata_loaded = hdul[extension].data
|
|
448
|
+
if auxdata_loaded.shape != self.data.shape:
|
|
449
|
+
print(f"data shape...: {self.data.shape}")
|
|
450
|
+
print(f"auxdata shape: {auxdata_loaded.shape}")
|
|
451
|
+
raise ValueError("Auxiliary file has different shape.")
|
|
452
|
+
self.auxfile = auxfile
|
|
453
|
+
self.auxdata = auxdata_loaded
|
|
454
|
+
self.extension_auxfile = extension
|
|
455
|
+
print(f"Loaded auxiliary data from {auxfile}")
|
|
456
|
+
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
457
|
+
except Exception as e:
|
|
458
|
+
print(f"Error loading auxiliary FITS file: {e}")
|
|
459
|
+
|
|
390
460
|
def save_fits_file(self):
|
|
391
461
|
"""Save the cleaned FITS file.
|
|
392
462
|
|
|
@@ -450,74 +520,153 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
450
520
|
and canvas for image display, along with the toolbar for navigation.
|
|
451
521
|
The relevant attributes are stored in the instance for later use.
|
|
452
522
|
"""
|
|
523
|
+
# Define instance of TrackedTkButton, that facilitates to show help information
|
|
524
|
+
# for each button displayed in the current application window.
|
|
525
|
+
tkbutton = TrackedTkButton(self.root)
|
|
526
|
+
|
|
453
527
|
# Row 1 of buttons
|
|
454
528
|
self.button_frame1 = tk.Frame(self.root)
|
|
455
529
|
self.button_frame1.pack(pady=5)
|
|
456
|
-
self.run_lacosmic_button =
|
|
530
|
+
self.run_lacosmic_button = tkbutton.new(
|
|
531
|
+
self.button_frame1,
|
|
532
|
+
text="Run L.A.Cosmic",
|
|
533
|
+
command=self.run_lacosmic,
|
|
534
|
+
help_text="Run the L.A.Cosmic algorithm to detect cosmic rays in the image.",
|
|
535
|
+
)
|
|
457
536
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
458
|
-
self.
|
|
459
|
-
self.button_frame1,
|
|
537
|
+
self.load_auxdata_button = tkbutton.new(
|
|
538
|
+
self.button_frame1,
|
|
539
|
+
text="Load auxdata",
|
|
540
|
+
command=self.load_auxdata_from_file,
|
|
541
|
+
help_text="Load an auxiliary FITS file for display.",
|
|
542
|
+
)
|
|
543
|
+
self.load_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
544
|
+
self.load_detected_cr_button = tkbutton.new(
|
|
545
|
+
self.button_frame1,
|
|
546
|
+
text="Load CR mask",
|
|
547
|
+
command=self.load_detected_cr_from_file,
|
|
548
|
+
help_text="Load a previously saved cosmic ray mask from a FITS file.",
|
|
460
549
|
)
|
|
461
550
|
self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
462
|
-
self.replace_detected_cr_button =
|
|
463
|
-
self.button_frame1,
|
|
551
|
+
self.replace_detected_cr_button = tkbutton.new(
|
|
552
|
+
self.button_frame1,
|
|
553
|
+
text="Replace detected CRs",
|
|
554
|
+
command=self.apply_lacosmic,
|
|
555
|
+
help_text="Apply the cleaning to the detected cosmic rays.",
|
|
464
556
|
)
|
|
465
557
|
self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
466
558
|
self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
467
|
-
self.
|
|
468
|
-
self.button_frame1,
|
|
559
|
+
self.review_detected_cr_button = tkbutton.new(
|
|
560
|
+
self.button_frame1,
|
|
561
|
+
text="Review detected CRs",
|
|
562
|
+
command=lambda: self.review_detected_cr(1),
|
|
563
|
+
help_text="Review the detected cosmic rays.",
|
|
469
564
|
)
|
|
470
|
-
self.
|
|
471
|
-
self.
|
|
565
|
+
self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
566
|
+
self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
472
567
|
|
|
473
568
|
# Row 2 of buttons
|
|
474
569
|
self.button_frame2 = tk.Frame(self.root)
|
|
475
570
|
self.button_frame2.pack(pady=5)
|
|
476
|
-
self.
|
|
571
|
+
self.use_cursor = False
|
|
572
|
+
self.use_cursor_button = tkbutton.new(
|
|
573
|
+
self.button_frame2,
|
|
574
|
+
text="[c]ursor: OFF",
|
|
575
|
+
command=self.set_cursor_onoff,
|
|
576
|
+
help_text="Toggle the cursor ON or OFF (to select CR with mouse).",
|
|
577
|
+
alttext="[c]ursor: ??",
|
|
578
|
+
)
|
|
579
|
+
self.use_cursor_button.pack(side=tk.LEFT, padx=5)
|
|
580
|
+
self.toggle_auxdata_button = tkbutton.new(
|
|
581
|
+
self.button_frame2,
|
|
582
|
+
text="[t]oggle data",
|
|
583
|
+
command=self.toggle_auxdata,
|
|
584
|
+
help_text="Toggle the display of auxiliary data.",
|
|
585
|
+
)
|
|
477
586
|
self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
478
587
|
if self.auxdata is None:
|
|
479
588
|
self.toggle_auxdata_button.config(state=tk.DISABLED)
|
|
480
589
|
else:
|
|
481
590
|
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
482
591
|
self.image_aspect = "equal"
|
|
483
|
-
self.toggle_aspect_button =
|
|
484
|
-
self.button_frame2,
|
|
592
|
+
self.toggle_aspect_button = tkbutton.new(
|
|
593
|
+
self.button_frame2,
|
|
594
|
+
text=f"[a]spect: {self.image_aspect}",
|
|
595
|
+
command=self.toggle_aspect,
|
|
596
|
+
help_text="Toggle the image aspect ratio.",
|
|
485
597
|
)
|
|
486
598
|
self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
|
|
487
|
-
self.save_button =
|
|
599
|
+
self.save_button = tkbutton.new(
|
|
600
|
+
self.button_frame2,
|
|
601
|
+
text="Save cleaned FITS",
|
|
602
|
+
command=self.save_fits_file,
|
|
603
|
+
help_text="Save the cleaned FITS file.",
|
|
604
|
+
)
|
|
488
605
|
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
489
606
|
self.save_button.config(state=tk.DISABLED) # Initially disabled
|
|
490
|
-
self.stop_button =
|
|
607
|
+
self.stop_button = tkbutton.new(
|
|
608
|
+
self.button_frame2, text="Stop program", command=self.stop_app, help_text="Stop the application."
|
|
609
|
+
)
|
|
491
610
|
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
492
611
|
|
|
493
612
|
# Row 3 of buttons
|
|
494
613
|
self.button_frame3 = tk.Frame(self.root)
|
|
495
614
|
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
615
|
vmin, vmax = zscale(self.data)
|
|
500
|
-
self.vmin_button =
|
|
616
|
+
self.vmin_button = tkbutton.new(
|
|
617
|
+
self.button_frame3,
|
|
618
|
+
text=f"vmin: {vmin:.2f}",
|
|
619
|
+
command=self.set_vmin,
|
|
620
|
+
help_text="Set the minimum value for the display scale.",
|
|
621
|
+
alttext="vmin: ??",
|
|
622
|
+
)
|
|
501
623
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
502
|
-
self.vmax_button =
|
|
624
|
+
self.vmax_button = tkbutton.new(
|
|
625
|
+
self.button_frame3,
|
|
626
|
+
text=f"vmax: {vmax:.2f}",
|
|
627
|
+
command=self.set_vmax,
|
|
628
|
+
help_text="Set the maximum value for the display scale.",
|
|
629
|
+
alttext="vmax: ??",
|
|
630
|
+
)
|
|
503
631
|
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
504
|
-
self.set_minmax_button =
|
|
632
|
+
self.set_minmax_button = tkbutton.new(
|
|
633
|
+
self.button_frame3,
|
|
634
|
+
text="minmax [,]",
|
|
635
|
+
command=self.set_minmax,
|
|
636
|
+
help_text="Set the minimum and maximum values for the display scale.",
|
|
637
|
+
)
|
|
505
638
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
506
|
-
self.set_zscale_button =
|
|
639
|
+
self.set_zscale_button = tkbutton.new(
|
|
640
|
+
self.button_frame3,
|
|
641
|
+
text="zscale [/]",
|
|
642
|
+
command=self.set_zscale,
|
|
643
|
+
help_text="Set the display scale using zscale.",
|
|
644
|
+
)
|
|
507
645
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
508
646
|
if self.overplot_cr_pixels:
|
|
509
|
-
self.overplot_cr_button =
|
|
647
|
+
self.overplot_cr_button = tkbutton.new(
|
|
510
648
|
self.button_frame3,
|
|
511
649
|
text="CR overlay: ON ",
|
|
512
650
|
command=self.toggle_cr_overlay,
|
|
651
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
652
|
+
alttext="CR overlay: ??",
|
|
513
653
|
)
|
|
514
654
|
else:
|
|
515
|
-
self.overplot_cr_button =
|
|
655
|
+
self.overplot_cr_button = tkbutton.new(
|
|
516
656
|
self.button_frame3,
|
|
517
657
|
text="CR overlay: OFF",
|
|
518
658
|
command=self.toggle_cr_overlay,
|
|
659
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
660
|
+
alttext="CR overlay: ??",
|
|
519
661
|
)
|
|
520
662
|
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
663
|
+
self.help_button = tkbutton.new(
|
|
664
|
+
self.button_frame3,
|
|
665
|
+
text="Help",
|
|
666
|
+
command=tkbutton.show_help,
|
|
667
|
+
help_text="Show help information for all buttons.",
|
|
668
|
+
)
|
|
669
|
+
self.help_button.pack(side=tk.LEFT, padx=5)
|
|
521
670
|
|
|
522
671
|
# Figure
|
|
523
672
|
self.plot_frame = tk.Frame(self.root)
|
|
@@ -606,6 +755,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
606
755
|
|
|
607
756
|
def run_lacosmic(self):
|
|
608
757
|
"""Run L.A.Cosmic to detect cosmic rays."""
|
|
758
|
+
if np.any(self.mask_crfound):
|
|
759
|
+
overwrite = messagebox.askyesno(
|
|
760
|
+
"Overwrite Cosmic Ray Mask",
|
|
761
|
+
"A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
|
|
762
|
+
)
|
|
763
|
+
if not overwrite:
|
|
764
|
+
return
|
|
609
765
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
610
766
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
611
767
|
editor_window = tk.Toplevel(self.root)
|
|
@@ -619,6 +775,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
619
775
|
ymin=self.last_ymin,
|
|
620
776
|
ymax=self.last_ymax,
|
|
621
777
|
imgshape=self.data.shape,
|
|
778
|
+
inbkg=self.last_inbkg,
|
|
779
|
+
extnum_inbkg=self.last_extnum_inbkg,
|
|
780
|
+
invar=self.last_invar,
|
|
781
|
+
extnum_invar=self.last_extnum_invar,
|
|
622
782
|
)
|
|
623
783
|
# Make it modal (blocks interaction with main window)
|
|
624
784
|
editor_window.transient(self.root)
|
|
@@ -633,6 +793,24 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
633
793
|
self.last_xmax = updated_params["xmax"]["value"]
|
|
634
794
|
self.last_ymin = updated_params["ymin"]["value"]
|
|
635
795
|
self.last_ymax = updated_params["ymax"]["value"]
|
|
796
|
+
self.last_inbkg = updated_params["inbkg"]["value"]
|
|
797
|
+
self.last_extnum_inbkg = updated_params["extnum_inbkg"]["value"]
|
|
798
|
+
self.last_invar = updated_params["invar"]["value"]
|
|
799
|
+
self.last_extnum_invar = updated_params["extnum_invar"]["value"]
|
|
800
|
+
if self.last_inbkg is not None:
|
|
801
|
+
with fits.open(self.last_inbkg, mode="readonly") as hdul_inbkg:
|
|
802
|
+
if self.last_extnum_inbkg < 0 or self.last_extnum_inbkg >= len(hdul_inbkg):
|
|
803
|
+
raise IndexError(f"Extension index {self.last_extnum_inbkg} out of range.")
|
|
804
|
+
self.inbkg_data = hdul_inbkg[self.last_extnum_inbkg].data.astype(np.float32)
|
|
805
|
+
else:
|
|
806
|
+
self.inbkg_data = None
|
|
807
|
+
if self.last_invar is not None:
|
|
808
|
+
with fits.open(self.last_invar, mode="readonly") as hdul_invar:
|
|
809
|
+
if self.last_extnum_invar < 0 or self.last_extnum_invar >= len(hdul_invar):
|
|
810
|
+
raise IndexError(f"Extension index {self.last_extnum_invar} out of range.")
|
|
811
|
+
self.invar_data = hdul_invar[self.last_extnum_invar].data.astype(np.float32)
|
|
812
|
+
else:
|
|
813
|
+
self.invar_data = None
|
|
636
814
|
usefulregion = SliceRegion2D(
|
|
637
815
|
f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
|
|
638
816
|
).python
|
|
@@ -651,14 +829,27 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
651
829
|
borderpadd = updated_params["borderpadd"]["value"]
|
|
652
830
|
cleandata_lacosmic, mask_crfound = lacosmicpad(
|
|
653
831
|
pad_width=borderpadd,
|
|
832
|
+
show_arguments=self.verbose,
|
|
654
833
|
ccd=self.data,
|
|
655
|
-
|
|
656
|
-
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
834
|
+
gain_apply=True, # Always apply gain
|
|
657
835
|
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
658
836
|
sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
|
|
659
837
|
objlim=self.lacosmic_params["run1_objlim"]["value"],
|
|
838
|
+
gain=self.lacosmic_params["run1_gain"]["value"],
|
|
839
|
+
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
840
|
+
satlevel=self.lacosmic_params["run1_satlevel"]["value"],
|
|
660
841
|
niter=self.lacosmic_params["run1_niter"]["value"],
|
|
842
|
+
sepmed=self.lacosmic_params["run1_sepmed"]["value"],
|
|
843
|
+
cleantype=self.lacosmic_params["run1_cleantype"]["value"],
|
|
844
|
+
fsmode=self.lacosmic_params["run1_fsmode"]["value"],
|
|
845
|
+
psfmodel=self.lacosmic_params["run1_psfmodel"]["value"],
|
|
846
|
+
psffwhm_x=self.lacosmic_params["run1_psffwhm_x"]["value"],
|
|
847
|
+
psffwhm_y=self.lacosmic_params["run1_psffwhm_y"]["value"],
|
|
848
|
+
psfsize=self.lacosmic_params["run1_psfsize"]["value"],
|
|
849
|
+
psfbeta=self.lacosmic_params["run1_psfbeta"]["value"],
|
|
661
850
|
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
851
|
+
inbkg=self.inbkg_data,
|
|
852
|
+
invar=self.invar_data,
|
|
662
853
|
)
|
|
663
854
|
# Apply usefulmask to consider only selected region
|
|
664
855
|
cleandata_lacosmic *= usefulmask
|
|
@@ -668,14 +859,27 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
668
859
|
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
669
860
|
cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
|
|
670
861
|
pad_width=borderpadd,
|
|
862
|
+
show_arguments=self.verbose,
|
|
671
863
|
ccd=self.data,
|
|
672
|
-
|
|
673
|
-
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
864
|
+
gain_apply=True, # Always apply gain
|
|
674
865
|
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
675
866
|
sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
|
|
676
867
|
objlim=self.lacosmic_params["run2_objlim"]["value"],
|
|
868
|
+
gain=self.lacosmic_params["run2_gain"]["value"],
|
|
869
|
+
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
870
|
+
satlevel=self.lacosmic_params["run2_satlevel"]["value"],
|
|
677
871
|
niter=self.lacosmic_params["run2_niter"]["value"],
|
|
872
|
+
sepmed=self.lacosmic_params["run2_sepmed"]["value"],
|
|
873
|
+
cleantype=self.lacosmic_params["run2_cleantype"]["value"],
|
|
874
|
+
fsmode=self.lacosmic_params["run2_fsmode"]["value"],
|
|
875
|
+
psfmodel=self.lacosmic_params["run2_psfmodel"]["value"],
|
|
876
|
+
psffwhm_x=self.lacosmic_params["run2_psffwhm_x"]["value"],
|
|
877
|
+
psffwhm_y=self.lacosmic_params["run2_psffwhm_y"]["value"],
|
|
878
|
+
psfsize=self.lacosmic_params["run2_psfsize"]["value"],
|
|
879
|
+
psfbeta=self.lacosmic_params["run2_psfbeta"]["value"],
|
|
678
880
|
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
881
|
+
inbkg=self.inbkg_data,
|
|
882
|
+
invar=self.invar_data,
|
|
679
883
|
)
|
|
680
884
|
# Apply usefulmask to consider only selected region
|
|
681
885
|
cleandata_lacosmic2 *= usefulmask
|
|
@@ -743,7 +947,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
743
947
|
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
744
948
|
last_npoints=self.last_npoints,
|
|
745
949
|
last_degree=self.last_degree,
|
|
950
|
+
last_maskfill_size=self.last_maskfill_size,
|
|
951
|
+
last_maskfill_operator=self.last_maskfill_operator,
|
|
952
|
+
last_maskfill_smooth=self.last_maskfill_smooth,
|
|
953
|
+
last_maskfill_verbose=self.last_maskfill_verbose,
|
|
746
954
|
auxdata=self.auxdata,
|
|
955
|
+
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
747
956
|
xmin=self.last_xmin,
|
|
748
957
|
xmax=self.last_xmax,
|
|
749
958
|
ymin=self.last_ymin,
|
|
@@ -760,8 +969,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
760
969
|
if cleaning_method is None:
|
|
761
970
|
print("Interpolation method selection cancelled. No cleaning applied!")
|
|
762
971
|
return
|
|
972
|
+
# Update last employed parameters
|
|
763
973
|
self.last_npoints = editor.npoints
|
|
764
974
|
self.last_degree = editor.degree
|
|
975
|
+
self.last_maskfill_size = editor.maskfill_size
|
|
976
|
+
self.last_maskfill_operator = editor.maskfill_operator
|
|
977
|
+
self.last_maskfill_smooth = editor.maskfill_smooth
|
|
978
|
+
self.last_maskfill_verbose = editor.maskfill_verbose
|
|
765
979
|
cleaning_region = SliceRegion2D(
|
|
766
980
|
f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
|
|
767
981
|
).python
|
|
@@ -783,12 +997,19 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
783
997
|
data_has_been_modified = True
|
|
784
998
|
elif cleaning_method == "maskfill":
|
|
785
999
|
# Replace detected CR pixels with local median values
|
|
1000
|
+
print(
|
|
1001
|
+
f"Maskfill parameters: size={self.last_maskfill_size}, "
|
|
1002
|
+
f"operator={self.last_maskfill_operator}, "
|
|
1003
|
+
f"smooth={self.last_maskfill_smooth}, "
|
|
1004
|
+
f"verbose={self.last_maskfill_verbose}"
|
|
1005
|
+
)
|
|
786
1006
|
smoothed_output, _ = maskfill(
|
|
787
1007
|
input_image=self.data,
|
|
788
1008
|
mask=mask_crfound_region,
|
|
789
|
-
size=
|
|
790
|
-
operator=
|
|
791
|
-
smooth=
|
|
1009
|
+
size=self.last_maskfill_size,
|
|
1010
|
+
operator=self.last_maskfill_operator,
|
|
1011
|
+
smooth=self.last_maskfill_smooth,
|
|
1012
|
+
verbose=self.last_maskfill_verbose,
|
|
792
1013
|
)
|
|
793
1014
|
self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
|
|
794
1015
|
# update mask_fixed to include the newly fixed pixels
|
|
@@ -891,11 +1112,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
891
1112
|
if data_has_been_modified:
|
|
892
1113
|
self.save_button.config(state=tk.NORMAL)
|
|
893
1114
|
if self.num_features == 0:
|
|
894
|
-
self.
|
|
1115
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
895
1116
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
896
1117
|
self.update_cr_overlay()
|
|
897
1118
|
|
|
898
|
-
def
|
|
1119
|
+
def review_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
|
|
899
1120
|
"""Open a window to examine and possibly clean detected cosmic rays."""
|
|
900
1121
|
self.working_in_review_window = True
|
|
901
1122
|
review_window = tk.Toplevel(self.root)
|
|
@@ -906,6 +1127,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
906
1127
|
tmp_cr_labels[iypix - 1, ixpix - 1] = 1
|
|
907
1128
|
review = ReviewCosmicRay(
|
|
908
1129
|
root=review_window,
|
|
1130
|
+
root_width=self.width,
|
|
1131
|
+
root_height=self.height,
|
|
909
1132
|
data=self.data,
|
|
910
1133
|
auxdata=self.auxdata,
|
|
911
1134
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -920,6 +1143,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
920
1143
|
else:
|
|
921
1144
|
review = ReviewCosmicRay(
|
|
922
1145
|
root=review_window,
|
|
1146
|
+
root_width=self.width,
|
|
1147
|
+
root_height=self.height,
|
|
923
1148
|
data=self.data,
|
|
924
1149
|
auxdata=self.auxdata,
|
|
925
1150
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -961,7 +1186,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
961
1186
|
if review.num_cr_cleaned > 0:
|
|
962
1187
|
self.save_button.config(state=tk.NORMAL)
|
|
963
1188
|
if self.num_features == 0:
|
|
964
|
-
self.
|
|
1189
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
965
1190
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
966
1191
|
self.update_cr_overlay()
|
|
967
1192
|
|
|
@@ -1069,4 +1294,4 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
1069
1294
|
else:
|
|
1070
1295
|
ixpix = None
|
|
1071
1296
|
iypix = None
|
|
1072
|
-
self.
|
|
1297
|
+
self.review_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)
|