teareduce 0.5.8__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 +8 -1
- teareduce/cleanest/__main__.py +11 -6
- teareduce/cleanest/askextension.py +85 -0
- teareduce/cleanest/cosmicraycleanerapp.py +297 -65
- 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 +100 -2
- teareduce/cleanest/parametereditor.py +279 -51
- teareduce/cleanest/reviewcosmicray.py +285 -83
- teareduce/cleanest/trackedbutton.py +73 -0
- teareduce/version.py +1 -1
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/METADATA +17 -5
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/RECORD +19 -16
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/WHEEL +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.8.dist-info → teareduce-0.6.0.dist-info}/top_level.txt +0 -0
|
@@ -17,7 +17,15 @@ from tkinter import simpledialog
|
|
|
17
17
|
import sys
|
|
18
18
|
|
|
19
19
|
from astropy.io import fits
|
|
20
|
-
|
|
20
|
+
|
|
21
|
+
try:
|
|
22
|
+
from maskfill import maskfill
|
|
23
|
+
except ModuleNotFoundError as e:
|
|
24
|
+
raise ModuleNotFoundError(
|
|
25
|
+
"The 'teareduce.cleanest' module requires the 'ccdproc' and 'maskfill' packages. "
|
|
26
|
+
"Please install teareduce with the 'cleanest' extra dependencies: "
|
|
27
|
+
"`pip install teareduce[cleanest]`."
|
|
28
|
+
) from e
|
|
21
29
|
import matplotlib.pyplot as plt
|
|
22
30
|
from matplotlib.backend_bases import key_press_handler
|
|
23
31
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
@@ -27,10 +35,15 @@ import os
|
|
|
27
35
|
from pathlib import Path
|
|
28
36
|
from rich import print
|
|
29
37
|
|
|
38
|
+
from .askextension import ask_extension_input_image
|
|
30
39
|
from .centerchildparent import center_on_parent
|
|
31
40
|
from .definitions import lacosmic_default_dict
|
|
32
41
|
from .definitions import DEFAULT_NPOINTS_INTERP
|
|
33
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
|
|
34
47
|
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
35
48
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_X
|
|
36
49
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
|
|
@@ -45,12 +58,14 @@ from .interpolationeditor import InterpolationEditor
|
|
|
45
58
|
from .imagedisplay import ImageDisplay
|
|
46
59
|
from .lacosmicpad import lacosmicpad
|
|
47
60
|
from .mergemasks import merge_peak_tail_masks
|
|
61
|
+
from .modalprogressbar import ModalProgressBar
|
|
48
62
|
from .parametereditor import ParameterEditor
|
|
49
63
|
from .reviewcosmicray import ReviewCosmicRay
|
|
50
|
-
from .
|
|
64
|
+
from .trackedbutton import TrackedTkButton
|
|
51
65
|
|
|
52
66
|
from ..imshow import imshow
|
|
53
67
|
from ..sliceregion import SliceRegion2D
|
|
68
|
+
from ..version import VERSION
|
|
54
69
|
from ..zscale import zscale
|
|
55
70
|
|
|
56
71
|
import matplotlib
|
|
@@ -120,7 +135,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
120
135
|
Update the overlay of cosmic ray pixels on the image.
|
|
121
136
|
apply_lacosmic()
|
|
122
137
|
Apply the L.A.Cosmic algorithm to the data.
|
|
123
|
-
|
|
138
|
+
review_detected_cr()
|
|
124
139
|
Examine detected cosmic rays.
|
|
125
140
|
stop_app()
|
|
126
141
|
Stop the application.
|
|
@@ -175,10 +190,26 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
175
190
|
last_ymax : int
|
|
176
191
|
Last used maximum y-coordinate for region selection.
|
|
177
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.
|
|
178
201
|
last_npoints : int
|
|
179
202
|
Last used number of points for interpolation.
|
|
180
203
|
last_degree : int
|
|
181
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.
|
|
182
213
|
cleandata_lacosmic : np.ndarray
|
|
183
214
|
The cleaned data returned from L.A.Cosmic.
|
|
184
215
|
cr_labels : np.ndarray
|
|
@@ -189,13 +220,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
189
220
|
Flag to indicate if the review window is active.
|
|
190
221
|
"""
|
|
191
222
|
self.root = root
|
|
192
|
-
# self.root.geometry("
|
|
223
|
+
# self.root.geometry("800x700+50+0") # This does not work in Fedora
|
|
193
224
|
self.width = width
|
|
194
225
|
self.height = height
|
|
195
226
|
self.verbose = verbose
|
|
196
227
|
self.root.minsize(self.width, self.height)
|
|
197
228
|
self.root.update_idletasks()
|
|
198
|
-
self.root.title("Cosmic Ray Cleaner")
|
|
229
|
+
self.root.title(f"Cosmic Ray Cleaner (TEA version {VERSION})")
|
|
199
230
|
self.fontfamily = fontfamily
|
|
200
231
|
self.fontsize = fontsize
|
|
201
232
|
self.default_font = tkfont.nametofont("TkDefaultFont")
|
|
@@ -218,8 +249,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
218
249
|
self.last_xmax = self.data.shape[1]
|
|
219
250
|
self.last_ymin = 1
|
|
220
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
|
|
221
258
|
self.last_npoints = DEFAULT_NPOINTS_INTERP
|
|
222
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
|
|
223
264
|
self.create_widgets()
|
|
224
265
|
self.cleandata_lacosmic = None
|
|
225
266
|
self.cr_labels = None
|
|
@@ -228,7 +269,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
228
269
|
|
|
229
270
|
def process_detected_cr(self, dilation):
|
|
230
271
|
"""Process the detected cosmic ray mask.
|
|
231
|
-
|
|
272
|
+
|
|
232
273
|
Parameters
|
|
233
274
|
----------
|
|
234
275
|
dilation : int
|
|
@@ -239,21 +280,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
239
280
|
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
240
281
|
if dilation > 0:
|
|
241
282
|
# Dilate the mask by the specified number of pixels
|
|
242
|
-
self.mask_crfound = dilatemask(
|
|
243
|
-
mask=self.mask_crfound, iterations=dilation, connectivity=1
|
|
244
|
-
)
|
|
283
|
+
self.mask_crfound = dilatemask(mask=self.mask_crfound, iterations=dilation, connectivity=1)
|
|
245
284
|
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
246
285
|
sdum = str(num_cr_pixels_after_dilation)
|
|
247
286
|
else:
|
|
248
287
|
sdum = str(num_cr_pixels_before_dilation)
|
|
249
|
-
print(
|
|
250
|
-
"Number of cosmic ray pixels detected..........: "
|
|
251
|
-
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
252
|
-
)
|
|
288
|
+
print("Number of cosmic ray pixels detected..........: " f"{num_cr_pixels_before_dilation:>{len(sdum)}}")
|
|
253
289
|
if dilation > 0:
|
|
254
290
|
print(
|
|
255
|
-
f"Number of cosmic ray pixels after dilation....: "
|
|
256
|
-
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)}}"
|
|
257
292
|
)
|
|
258
293
|
# Label connected components in the mask; note that by default,
|
|
259
294
|
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
@@ -262,7 +297,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
262
297
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
263
298
|
print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
264
299
|
self.replace_detected_cr_button.config(state=tk.NORMAL)
|
|
265
|
-
self.
|
|
300
|
+
self.review_detected_cr_button.config(state=tk.NORMAL)
|
|
266
301
|
self.update_cr_overlay()
|
|
267
302
|
self.use_cursor = True
|
|
268
303
|
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
@@ -271,10 +306,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
271
306
|
self.cr_labels = None
|
|
272
307
|
self.num_features = 0
|
|
273
308
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
274
|
-
self.
|
|
309
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
275
310
|
|
|
276
311
|
def load_detected_cr_from_file(self):
|
|
277
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
|
|
278
320
|
crmask_file = filedialog.askopenfilename(
|
|
279
321
|
initialdir=os.getcwd(),
|
|
280
322
|
title="Select FITS file with cosmic ray mask",
|
|
@@ -282,18 +324,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
282
324
|
)
|
|
283
325
|
if crmask_file:
|
|
284
326
|
print(f"Selected input FITS file: {crmask_file}")
|
|
285
|
-
extension =
|
|
286
|
-
"Select Extension",
|
|
287
|
-
f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
|
|
288
|
-
initialvalue=None,
|
|
289
|
-
)
|
|
290
|
-
try:
|
|
291
|
-
extension = int(extension)
|
|
292
|
-
except ValueError:
|
|
293
|
-
pass # Keep as string
|
|
294
|
-
dilation = simpledialog.askinteger(
|
|
295
|
-
"Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
|
|
296
|
-
)
|
|
327
|
+
extension = ask_extension_input_image(crmask_file, self.data.shape)
|
|
297
328
|
try:
|
|
298
329
|
with fits.open(crmask_file, mode="readonly") as hdul:
|
|
299
330
|
if isinstance(extension, int):
|
|
@@ -302,6 +333,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
302
333
|
else:
|
|
303
334
|
if extension not in hdul:
|
|
304
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).")
|
|
305
338
|
mask_crfound_loaded = hdul[extension].data.astype(bool)
|
|
306
339
|
if mask_crfound_loaded.shape != self.data.shape:
|
|
307
340
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -309,9 +342,14 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
309
342
|
raise ValueError("Cosmic ray mask has different shape.")
|
|
310
343
|
self.mask_crfound = mask_crfound_loaded
|
|
311
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
|
+
)
|
|
312
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
|
|
313
351
|
except Exception as e:
|
|
314
|
-
|
|
352
|
+
messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
|
|
315
353
|
|
|
316
354
|
def load_fits_file(self):
|
|
317
355
|
"""Load the FITS file and auxiliary file (if provided).
|
|
@@ -371,7 +409,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
371
409
|
else:
|
|
372
410
|
if self.extension_auxfile not in hdul_aux:
|
|
373
411
|
raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
|
|
374
|
-
print(
|
|
412
|
+
print(
|
|
413
|
+
f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}"
|
|
414
|
+
)
|
|
375
415
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
376
416
|
if self.auxdata.shape != self.data.shape:
|
|
377
417
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -380,6 +420,43 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
380
420
|
except Exception as e:
|
|
381
421
|
sys.exit(f"Error loading auxiliary FITS file: {e}")
|
|
382
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
|
+
|
|
383
460
|
def save_fits_file(self):
|
|
384
461
|
"""Save the cleaned FITS file.
|
|
385
462
|
|
|
@@ -443,74 +520,153 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
443
520
|
and canvas for image display, along with the toolbar for navigation.
|
|
444
521
|
The relevant attributes are stored in the instance for later use.
|
|
445
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
|
+
|
|
446
527
|
# Row 1 of buttons
|
|
447
528
|
self.button_frame1 = tk.Frame(self.root)
|
|
448
529
|
self.button_frame1.pack(pady=5)
|
|
449
|
-
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
|
+
)
|
|
450
536
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
451
|
-
self.
|
|
452
|
-
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.",
|
|
453
549
|
)
|
|
454
550
|
self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
455
|
-
self.replace_detected_cr_button =
|
|
456
|
-
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.",
|
|
457
556
|
)
|
|
458
557
|
self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
459
558
|
self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
460
|
-
self.
|
|
461
|
-
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.",
|
|
462
564
|
)
|
|
463
|
-
self.
|
|
464
|
-
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
|
|
465
567
|
|
|
466
568
|
# Row 2 of buttons
|
|
467
569
|
self.button_frame2 = tk.Frame(self.root)
|
|
468
570
|
self.button_frame2.pack(pady=5)
|
|
469
|
-
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
|
+
)
|
|
470
586
|
self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
471
587
|
if self.auxdata is None:
|
|
472
588
|
self.toggle_auxdata_button.config(state=tk.DISABLED)
|
|
473
589
|
else:
|
|
474
590
|
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
475
591
|
self.image_aspect = "equal"
|
|
476
|
-
self.toggle_aspect_button =
|
|
477
|
-
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.",
|
|
478
597
|
)
|
|
479
598
|
self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
|
|
480
|
-
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
|
+
)
|
|
481
605
|
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
482
606
|
self.save_button.config(state=tk.DISABLED) # Initially disabled
|
|
483
|
-
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
|
+
)
|
|
484
610
|
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
485
611
|
|
|
486
612
|
# Row 3 of buttons
|
|
487
613
|
self.button_frame3 = tk.Frame(self.root)
|
|
488
614
|
self.button_frame3.pack(pady=5)
|
|
489
|
-
self.use_cursor = False
|
|
490
|
-
self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
|
|
491
|
-
self.use_cursor_button.pack(side=tk.LEFT, padx=5)
|
|
492
615
|
vmin, vmax = zscale(self.data)
|
|
493
|
-
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
|
+
)
|
|
494
623
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
495
|
-
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
|
+
)
|
|
496
631
|
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
497
|
-
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
|
+
)
|
|
498
638
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
499
|
-
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
|
+
)
|
|
500
645
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
501
646
|
if self.overplot_cr_pixels:
|
|
502
|
-
self.overplot_cr_button =
|
|
647
|
+
self.overplot_cr_button = tkbutton.new(
|
|
503
648
|
self.button_frame3,
|
|
504
649
|
text="CR overlay: ON ",
|
|
505
650
|
command=self.toggle_cr_overlay,
|
|
651
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
652
|
+
alttext="CR overlay: ??",
|
|
506
653
|
)
|
|
507
654
|
else:
|
|
508
|
-
self.overplot_cr_button =
|
|
655
|
+
self.overplot_cr_button = tkbutton.new(
|
|
509
656
|
self.button_frame3,
|
|
510
657
|
text="CR overlay: OFF",
|
|
511
658
|
command=self.toggle_cr_overlay,
|
|
659
|
+
help_text="Toggle the cosmic ray overlay ON or OFF.",
|
|
660
|
+
alttext="CR overlay: ??",
|
|
512
661
|
)
|
|
513
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)
|
|
514
670
|
|
|
515
671
|
# Figure
|
|
516
672
|
self.plot_frame = tk.Frame(self.root)
|
|
@@ -599,6 +755,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
599
755
|
|
|
600
756
|
def run_lacosmic(self):
|
|
601
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
|
|
602
765
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
603
766
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
604
767
|
editor_window = tk.Toplevel(self.root)
|
|
@@ -612,6 +775,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
612
775
|
ymin=self.last_ymin,
|
|
613
776
|
ymax=self.last_ymax,
|
|
614
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,
|
|
615
782
|
)
|
|
616
783
|
# Make it modal (blocks interaction with main window)
|
|
617
784
|
editor_window.transient(self.root)
|
|
@@ -626,6 +793,24 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
626
793
|
self.last_xmax = updated_params["xmax"]["value"]
|
|
627
794
|
self.last_ymin = updated_params["ymin"]["value"]
|
|
628
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
|
|
629
814
|
usefulregion = SliceRegion2D(
|
|
630
815
|
f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
|
|
631
816
|
).python
|
|
@@ -644,14 +829,27 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
644
829
|
borderpadd = updated_params["borderpadd"]["value"]
|
|
645
830
|
cleandata_lacosmic, mask_crfound = lacosmicpad(
|
|
646
831
|
pad_width=borderpadd,
|
|
832
|
+
show_arguments=self.verbose,
|
|
647
833
|
ccd=self.data,
|
|
648
|
-
|
|
649
|
-
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
834
|
+
gain_apply=True, # Always apply gain
|
|
650
835
|
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
651
836
|
sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
|
|
652
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"],
|
|
653
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"],
|
|
654
850
|
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
851
|
+
inbkg=self.inbkg_data,
|
|
852
|
+
invar=self.invar_data,
|
|
655
853
|
)
|
|
656
854
|
# Apply usefulmask to consider only selected region
|
|
657
855
|
cleandata_lacosmic *= usefulmask
|
|
@@ -661,14 +859,27 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
661
859
|
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
662
860
|
cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
|
|
663
861
|
pad_width=borderpadd,
|
|
862
|
+
show_arguments=self.verbose,
|
|
664
863
|
ccd=self.data,
|
|
665
|
-
|
|
666
|
-
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
864
|
+
gain_apply=True, # Always apply gain
|
|
667
865
|
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
668
866
|
sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
|
|
669
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"],
|
|
670
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"],
|
|
671
880
|
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
881
|
+
inbkg=self.inbkg_data,
|
|
882
|
+
invar=self.invar_data,
|
|
672
883
|
)
|
|
673
884
|
# Apply usefulmask to consider only selected region
|
|
674
885
|
cleandata_lacosmic2 *= usefulmask
|
|
@@ -736,7 +947,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
736
947
|
last_dilation=self.lacosmic_params["dilation"]["value"],
|
|
737
948
|
last_npoints=self.last_npoints,
|
|
738
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,
|
|
739
954
|
auxdata=self.auxdata,
|
|
955
|
+
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
740
956
|
xmin=self.last_xmin,
|
|
741
957
|
xmax=self.last_xmax,
|
|
742
958
|
ymin=self.last_ymin,
|
|
@@ -753,8 +969,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
753
969
|
if cleaning_method is None:
|
|
754
970
|
print("Interpolation method selection cancelled. No cleaning applied!")
|
|
755
971
|
return
|
|
972
|
+
# Update last employed parameters
|
|
756
973
|
self.last_npoints = editor.npoints
|
|
757
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
|
|
758
979
|
cleaning_region = SliceRegion2D(
|
|
759
980
|
f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
|
|
760
981
|
).python
|
|
@@ -776,12 +997,19 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
776
997
|
data_has_been_modified = True
|
|
777
998
|
elif cleaning_method == "maskfill":
|
|
778
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
|
+
)
|
|
779
1006
|
smoothed_output, _ = maskfill(
|
|
780
1007
|
input_image=self.data,
|
|
781
1008
|
mask=mask_crfound_region,
|
|
782
|
-
size=
|
|
783
|
-
operator=
|
|
784
|
-
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,
|
|
785
1013
|
)
|
|
786
1014
|
self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
|
|
787
1015
|
# update mask_fixed to include the newly fixed pixels
|
|
@@ -884,11 +1112,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
884
1112
|
if data_has_been_modified:
|
|
885
1113
|
self.save_button.config(state=tk.NORMAL)
|
|
886
1114
|
if self.num_features == 0:
|
|
887
|
-
self.
|
|
1115
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
888
1116
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
889
1117
|
self.update_cr_overlay()
|
|
890
1118
|
|
|
891
|
-
def
|
|
1119
|
+
def review_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
|
|
892
1120
|
"""Open a window to examine and possibly clean detected cosmic rays."""
|
|
893
1121
|
self.working_in_review_window = True
|
|
894
1122
|
review_window = tk.Toplevel(self.root)
|
|
@@ -899,6 +1127,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
899
1127
|
tmp_cr_labels[iypix - 1, ixpix - 1] = 1
|
|
900
1128
|
review = ReviewCosmicRay(
|
|
901
1129
|
root=review_window,
|
|
1130
|
+
root_width=self.width,
|
|
1131
|
+
root_height=self.height,
|
|
902
1132
|
data=self.data,
|
|
903
1133
|
auxdata=self.auxdata,
|
|
904
1134
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -913,6 +1143,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
913
1143
|
else:
|
|
914
1144
|
review = ReviewCosmicRay(
|
|
915
1145
|
root=review_window,
|
|
1146
|
+
root_width=self.width,
|
|
1147
|
+
root_height=self.height,
|
|
916
1148
|
data=self.data,
|
|
917
1149
|
auxdata=self.auxdata,
|
|
918
1150
|
cleandata_lacosmic=self.cleandata_lacosmic,
|
|
@@ -954,7 +1186,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
954
1186
|
if review.num_cr_cleaned > 0:
|
|
955
1187
|
self.save_button.config(state=tk.NORMAL)
|
|
956
1188
|
if self.num_features == 0:
|
|
957
|
-
self.
|
|
1189
|
+
self.review_detected_cr_button.config(state=tk.DISABLED)
|
|
958
1190
|
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
959
1191
|
self.update_cr_overlay()
|
|
960
1192
|
|
|
@@ -1062,4 +1294,4 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
1062
1294
|
else:
|
|
1063
1295
|
ixpix = None
|
|
1064
1296
|
iypix = None
|
|
1065
|
-
self.
|
|
1297
|
+
self.review_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)
|