teareduce 0.5.5__py3-none-any.whl → 0.5.8__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 +3 -1
- teareduce/cleanest/__main__.py +65 -6
- teareduce/cleanest/cosmicraycleanerapp.py +303 -94
- teareduce/cleanest/definitions.py +1 -1
- teareduce/cleanest/{cleanest.py → interpolate.py} +10 -8
- teareduce/cleanest/interpolationeditor.py +1 -0
- teareduce/cleanest/lacosmicpad.py +57 -0
- teareduce/cleanest/mergemasks.py +58 -0
- teareduce/cleanest/modalprogressbar.py +2 -2
- teareduce/cleanest/parametereditor.py +14 -3
- teareduce/cleanest/reviewcosmicray.py +31 -2
- teareduce/tests/test_cleanest.py +11 -11
- teareduce/version.py +1 -1
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/METADATA +2 -1
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/RECORD +19 -17
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/WHEEL +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.8.dist-info}/top_level.txt +0 -0
teareduce/cleanest/__init__.py
CHANGED
teareduce/cleanest/__main__.py
CHANGED
|
@@ -10,8 +10,12 @@
|
|
|
10
10
|
"""Interactive Cosmic Ray cleaning tool."""
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
|
+
from ast import arg
|
|
13
14
|
import tkinter as tk
|
|
15
|
+
from tkinter import filedialog
|
|
16
|
+
from tkinter import simpledialog
|
|
14
17
|
import os
|
|
18
|
+
from pathlib import Path
|
|
15
19
|
import platform
|
|
16
20
|
from rich import print
|
|
17
21
|
from rich_argparse import RichHelpFormatter
|
|
@@ -21,6 +25,7 @@ from .definitions import DEFAULT_FONT_SIZE
|
|
|
21
25
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_X
|
|
22
26
|
from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
|
|
23
27
|
from .cosmicraycleanerapp import CosmicRayCleanerApp
|
|
28
|
+
from ..version import VERSION
|
|
24
29
|
|
|
25
30
|
import matplotlib
|
|
26
31
|
|
|
@@ -32,13 +37,13 @@ def main():
|
|
|
32
37
|
description="Interactive cosmic ray cleaner for FITS images.",
|
|
33
38
|
formatter_class=RichHelpFormatter,
|
|
34
39
|
)
|
|
35
|
-
parser.add_argument("input_fits", help="Path to the FITS file to be cleaned.")
|
|
36
|
-
parser.add_argument("--extension", type=
|
|
40
|
+
parser.add_argument("input_fits", nargs="?", default=None, help="Path to the FITS file to be cleaned.")
|
|
41
|
+
parser.add_argument("--extension", type=str, default="0", help="FITS extension to use (default: 0).")
|
|
37
42
|
parser.add_argument("--auxfile", type=str, default=None, help="Auxiliary FITS file")
|
|
38
43
|
parser.add_argument(
|
|
39
44
|
"--extension_auxfile",
|
|
40
|
-
type=
|
|
41
|
-
default=0,
|
|
45
|
+
type=str,
|
|
46
|
+
default="0",
|
|
42
47
|
help="FITS extension for auxiliary file (default: 0).",
|
|
43
48
|
)
|
|
44
49
|
parser.add_argument(
|
|
@@ -65,14 +70,67 @@ def main():
|
|
|
65
70
|
default=DEFAULT_TK_WINDOW_SIZE_Y,
|
|
66
71
|
help=f"Height of the GUI window in pixels (default: {DEFAULT_TK_WINDOW_SIZE_Y}).",
|
|
67
72
|
)
|
|
73
|
+
parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")
|
|
74
|
+
parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output.")
|
|
68
75
|
args = parser.parse_args()
|
|
69
76
|
|
|
77
|
+
# Welcome message
|
|
78
|
+
print("[bold green]Cosmic Ray Cleaner[/bold green]")
|
|
79
|
+
print("Interactive tool to clean cosmic rays from FITS images.")
|
|
80
|
+
print("teareduce version: " + VERSION)
|
|
81
|
+
print(f"https://nicocardiel.github.io/teareduce-cookbook/docs/cleanest/cleanest.html\n")
|
|
82
|
+
|
|
83
|
+
# If input_file is not provided, ask for it using a file dialog
|
|
84
|
+
if args.input_fits is None:
|
|
85
|
+
root = tk.Tk()
|
|
86
|
+
root.withdraw() # Hide the root window
|
|
87
|
+
args.input_fits = filedialog.askopenfilename(
|
|
88
|
+
title="Select FITS file to be cleaned",
|
|
89
|
+
filetypes=[("FITS files", "*.fits *.fit *.fts"), ("All files", "*.*")],
|
|
90
|
+
)
|
|
91
|
+
if not args.input_fits:
|
|
92
|
+
print("No input FITS file selected. Exiting.")
|
|
93
|
+
exit(1)
|
|
94
|
+
print(f"Selected input FITS file: {args.input_fits}")
|
|
95
|
+
args.extension = simpledialog.askstring(
|
|
96
|
+
"Select Extension",
|
|
97
|
+
f"\nEnter extension number or name for file:\n{Path(args.input_fits).name}",
|
|
98
|
+
initialvalue=args.extension,
|
|
99
|
+
)
|
|
100
|
+
# Ask for auxiliary file if not provided
|
|
101
|
+
if args.auxfile is None:
|
|
102
|
+
use_auxfile = tk.messagebox.askyesno(
|
|
103
|
+
"Auxiliary File",
|
|
104
|
+
"Do you want to use an auxiliary FITS file?",
|
|
105
|
+
default=tk.messagebox.NO,
|
|
106
|
+
)
|
|
107
|
+
if use_auxfile:
|
|
108
|
+
args.auxfile = filedialog.askopenfilename(
|
|
109
|
+
title="Select Auxiliary FITS file",
|
|
110
|
+
filetypes=[("FITS files", "*.fits *.fit *.fts"), ("All files", "*.*")],
|
|
111
|
+
initialfile=args.auxfile,
|
|
112
|
+
)
|
|
113
|
+
if not args.auxfile:
|
|
114
|
+
print("No auxiliary FITS file selected. Exiting.")
|
|
115
|
+
exit(1)
|
|
116
|
+
else:
|
|
117
|
+
use_auxfile = True
|
|
118
|
+
if use_auxfile:
|
|
119
|
+
print(f"Selected auxiliary FITS file: {args.auxfile}")
|
|
120
|
+
args.extension_auxfile = simpledialog.askstring(
|
|
121
|
+
"Select Extension for Auxiliary File",
|
|
122
|
+
f"\nEnter extension number or name for auxiliary file:\n{Path(args.auxfile).name}",
|
|
123
|
+
initialvalue=args.extension_auxfile,
|
|
124
|
+
)
|
|
125
|
+
root.destroy()
|
|
126
|
+
|
|
127
|
+
# Check that input files, and the corresponding extensions, exist
|
|
70
128
|
if not os.path.isfile(args.input_fits):
|
|
71
129
|
print(f"Error: File '{args.input_fits}' does not exist.")
|
|
72
|
-
|
|
130
|
+
exit(1)
|
|
73
131
|
if args.auxfile is not None and not os.path.isfile(args.auxfile):
|
|
74
132
|
print(f"Error: Auxiliary file '{args.auxfile}' does not exist.")
|
|
75
|
-
|
|
133
|
+
exit(1)
|
|
76
134
|
|
|
77
135
|
# Initialize Tkinter root
|
|
78
136
|
root = tk.Tk()
|
|
@@ -100,6 +158,7 @@ def main():
|
|
|
100
158
|
fontsize=args.fontsize,
|
|
101
159
|
width=args.width,
|
|
102
160
|
height=args.height,
|
|
161
|
+
verbose=args.verbose,
|
|
103
162
|
)
|
|
104
163
|
|
|
105
164
|
# Execute
|
|
@@ -13,16 +13,18 @@ import tkinter as tk
|
|
|
13
13
|
from tkinter import filedialog
|
|
14
14
|
from tkinter import font as tkfont
|
|
15
15
|
from tkinter import messagebox
|
|
16
|
+
from tkinter import simpledialog
|
|
16
17
|
import sys
|
|
17
18
|
|
|
18
19
|
from astropy.io import fits
|
|
19
|
-
from
|
|
20
|
+
from maskfill import maskfill
|
|
20
21
|
import matplotlib.pyplot as plt
|
|
21
22
|
from matplotlib.backend_bases import key_press_handler
|
|
22
23
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
23
24
|
from scipy import ndimage
|
|
24
25
|
import numpy as np
|
|
25
26
|
import os
|
|
27
|
+
from pathlib import Path
|
|
26
28
|
from rich import print
|
|
27
29
|
|
|
28
30
|
from .centerchildparent import center_on_parent
|
|
@@ -41,6 +43,8 @@ from .interpolation_x import interpolation_x
|
|
|
41
43
|
from .interpolation_y import interpolation_y
|
|
42
44
|
from .interpolationeditor import InterpolationEditor
|
|
43
45
|
from .imagedisplay import ImageDisplay
|
|
46
|
+
from .lacosmicpad import lacosmicpad
|
|
47
|
+
from .mergemasks import merge_peak_tail_masks
|
|
44
48
|
from .parametereditor import ParameterEditor
|
|
45
49
|
from .reviewcosmicray import ReviewCosmicRay
|
|
46
50
|
from .modalprogressbar import ModalProgressBar
|
|
@@ -61,13 +65,14 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
61
65
|
self,
|
|
62
66
|
root,
|
|
63
67
|
input_fits,
|
|
64
|
-
extension=0,
|
|
68
|
+
extension="0",
|
|
65
69
|
auxfile=None,
|
|
66
|
-
extension_auxfile=0,
|
|
70
|
+
extension_auxfile="0",
|
|
67
71
|
fontfamily=DEFAULT_FONT_FAMILY,
|
|
68
72
|
fontsize=DEFAULT_FONT_SIZE,
|
|
69
73
|
width=DEFAULT_TK_WINDOW_SIZE_X,
|
|
70
74
|
height=DEFAULT_TK_WINDOW_SIZE_Y,
|
|
75
|
+
verbose=False,
|
|
71
76
|
):
|
|
72
77
|
"""
|
|
73
78
|
Initialize the application.
|
|
@@ -78,19 +83,29 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
78
83
|
The main Tkinter window.
|
|
79
84
|
input_fits : str
|
|
80
85
|
Path to the FITS file to be cleaned.
|
|
81
|
-
extension :
|
|
82
|
-
FITS extension to use (default is 0).
|
|
86
|
+
extension : str, optional
|
|
87
|
+
FITS extension to use (default is "0").
|
|
83
88
|
auxfile : str, optional
|
|
84
89
|
Path to an auxiliary FITS file (default is None).
|
|
85
|
-
extension_auxfile :
|
|
86
|
-
FITS extension for auxiliary file (default is 0).
|
|
90
|
+
extension_auxfile : str, optional
|
|
91
|
+
FITS extension for auxiliary file (default is "0").
|
|
87
92
|
fontfamily : str, optional
|
|
88
93
|
Font family for the GUI (default is "Helvetica").
|
|
89
94
|
fontsize : int, optional
|
|
90
95
|
Font size for the GUI (default is 14).
|
|
96
|
+
width : int, optional
|
|
97
|
+
Width of the GUI window in pixels (default is 800).
|
|
98
|
+
height : int, optional
|
|
99
|
+
Height of the GUI window in pixels (default is 600).
|
|
100
|
+
verbose : bool, optional
|
|
101
|
+
Enable verbose output (default is False).
|
|
91
102
|
|
|
92
103
|
Methods
|
|
93
104
|
-------
|
|
105
|
+
process_detected_cr(dilation)
|
|
106
|
+
Process the detected cosmic ray mask.
|
|
107
|
+
load_detected_cr_from_file()
|
|
108
|
+
Load detected cosmic ray mask from a FITS file.
|
|
94
109
|
load_fits_file()
|
|
95
110
|
Load the FITS file and auxiliary file (if provided).
|
|
96
111
|
save_fits_file()
|
|
@@ -124,6 +139,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
124
139
|
Font size for the GUI.
|
|
125
140
|
default_font : tkfont.Font
|
|
126
141
|
The default font used in the GUI.
|
|
142
|
+
width : int
|
|
143
|
+
Width of the GUI window in pixels.
|
|
144
|
+
height : int
|
|
145
|
+
Height of the GUI window in pixels.
|
|
146
|
+
verbose : bool
|
|
147
|
+
Enable verbose output.
|
|
127
148
|
lacosmic_params : dict
|
|
128
149
|
Dictionary of L.A.Cosmic parameters.
|
|
129
150
|
input_fits : str
|
|
@@ -171,6 +192,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
171
192
|
# self.root.geometry("800x800+50+0") # This does not work in Fedora
|
|
172
193
|
self.width = width
|
|
173
194
|
self.height = height
|
|
195
|
+
self.verbose = verbose
|
|
174
196
|
self.root.minsize(self.width, self.height)
|
|
175
197
|
self.root.update_idletasks()
|
|
176
198
|
self.root.title("Cosmic Ray Cleaner")
|
|
@@ -181,6 +203,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
181
203
|
family=fontfamily, size=fontsize, weight="normal", slant="roman", underline=0, overstrike=0
|
|
182
204
|
)
|
|
183
205
|
self.lacosmic_params = lacosmic_default_dict.copy()
|
|
206
|
+
self.lacosmic_params["run1_verbose"]["value"] = self.verbose
|
|
207
|
+
self.lacosmic_params["run2_verbose"]["value"] = self.verbose
|
|
184
208
|
self.input_fits = input_fits
|
|
185
209
|
self.extension = extension
|
|
186
210
|
self.data = None
|
|
@@ -202,6 +226,93 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
202
226
|
self.num_features = 0
|
|
203
227
|
self.working_in_review_window = False
|
|
204
228
|
|
|
229
|
+
def process_detected_cr(self, dilation):
|
|
230
|
+
"""Process the detected cosmic ray mask.
|
|
231
|
+
|
|
232
|
+
Parameters
|
|
233
|
+
----------
|
|
234
|
+
dilation : int
|
|
235
|
+
Number of pixels to dilate the cosmic ray mask.
|
|
236
|
+
"""
|
|
237
|
+
# Process the mask: dilation and labeling
|
|
238
|
+
if np.any(self.mask_crfound):
|
|
239
|
+
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
240
|
+
if dilation > 0:
|
|
241
|
+
# Dilate the mask by the specified number of pixels
|
|
242
|
+
self.mask_crfound = dilatemask(
|
|
243
|
+
mask=self.mask_crfound, iterations=dilation, connectivity=1
|
|
244
|
+
)
|
|
245
|
+
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
246
|
+
sdum = str(num_cr_pixels_after_dilation)
|
|
247
|
+
else:
|
|
248
|
+
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
|
+
)
|
|
253
|
+
if dilation > 0:
|
|
254
|
+
print(
|
|
255
|
+
f"Number of cosmic ray pixels after dilation....: "
|
|
256
|
+
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
257
|
+
)
|
|
258
|
+
# Label connected components in the mask; note that by default,
|
|
259
|
+
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
260
|
+
# diagonal connections too, so we define a 3x3 square.
|
|
261
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
262
|
+
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
263
|
+
print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
264
|
+
self.replace_detected_cr_button.config(state=tk.NORMAL)
|
|
265
|
+
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
266
|
+
self.update_cr_overlay()
|
|
267
|
+
self.use_cursor = True
|
|
268
|
+
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
269
|
+
else:
|
|
270
|
+
print("No cosmic ray pixels detected!")
|
|
271
|
+
self.cr_labels = None
|
|
272
|
+
self.num_features = 0
|
|
273
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
274
|
+
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
275
|
+
|
|
276
|
+
def load_detected_cr_from_file(self):
|
|
277
|
+
"""Load detected cosmic ray mask from a FITS file."""
|
|
278
|
+
crmask_file = filedialog.askopenfilename(
|
|
279
|
+
initialdir=os.getcwd(),
|
|
280
|
+
title="Select FITS file with cosmic ray mask",
|
|
281
|
+
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
282
|
+
)
|
|
283
|
+
if crmask_file:
|
|
284
|
+
print(f"Selected input FITS file: {crmask_file}")
|
|
285
|
+
extension = simpledialog.askstring(
|
|
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
|
+
)
|
|
297
|
+
try:
|
|
298
|
+
with fits.open(crmask_file, mode="readonly") as hdul:
|
|
299
|
+
if isinstance(extension, int):
|
|
300
|
+
if extension < 0 or extension >= len(hdul):
|
|
301
|
+
raise IndexError(f"Extension index {extension} out of range.")
|
|
302
|
+
else:
|
|
303
|
+
if extension not in hdul:
|
|
304
|
+
raise KeyError(f"Extension name '{extension}' not found.")
|
|
305
|
+
mask_crfound_loaded = hdul[extension].data.astype(bool)
|
|
306
|
+
if mask_crfound_loaded.shape != self.data.shape:
|
|
307
|
+
print(f"data shape...: {self.data.shape}")
|
|
308
|
+
print(f"mask shape...: {mask_crfound_loaded.shape}")
|
|
309
|
+
raise ValueError("Cosmic ray mask has different shape.")
|
|
310
|
+
self.mask_crfound = mask_crfound_loaded
|
|
311
|
+
print(f"Loaded cosmic ray mask from {crmask_file}")
|
|
312
|
+
self.process_detected_cr(dilation=dilation)
|
|
313
|
+
except Exception as e:
|
|
314
|
+
print(f"Error loading cosmic ray mask: {e}")
|
|
315
|
+
|
|
205
316
|
def load_fits_file(self):
|
|
206
317
|
"""Load the FITS file and auxiliary file (if provided).
|
|
207
318
|
|
|
@@ -216,8 +327,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
216
327
|
provided, it also loads the auxiliary data from the specified extension.
|
|
217
328
|
The loaded data is stored in `self.data` and `self.auxdata` attributes.
|
|
218
329
|
"""
|
|
330
|
+
# check if extension is compatible with an integer
|
|
331
|
+
try:
|
|
332
|
+
extnum = int(self.extension)
|
|
333
|
+
self.extension = extnum
|
|
334
|
+
except ValueError:
|
|
335
|
+
# Keep as string (delaying checking until opening the file)
|
|
336
|
+
self.extension = self.extension.upper() # Convert to uppercase
|
|
219
337
|
try:
|
|
220
338
|
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
339
|
+
if isinstance(self.extension, int):
|
|
340
|
+
if self.extension < 0 or self.extension >= len(hdul):
|
|
341
|
+
raise IndexError(f"Extension index {self.extension} out of range.")
|
|
342
|
+
else:
|
|
343
|
+
if self.extension not in hdul:
|
|
344
|
+
raise KeyError(f"Extension name '{self.extension}' not found.")
|
|
345
|
+
print(f"Reading file [bold green]{self.input_fits}[/bold green], extension {self.extension}")
|
|
221
346
|
self.data = hdul[self.extension].data
|
|
222
347
|
if "CRMASK" in hdul:
|
|
223
348
|
self.mask_fixed = hdul["CRMASK"].data.astype(bool)
|
|
@@ -225,13 +350,28 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
225
350
|
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
226
351
|
except Exception as e:
|
|
227
352
|
print(f"Error loading FITS file: {e}")
|
|
353
|
+
sys.exit(1)
|
|
228
354
|
self.mask_crfound = np.zeros(self.data.shape, dtype=bool)
|
|
229
355
|
naxis2, naxis1 = self.data.shape
|
|
230
356
|
self.region = SliceRegion2D(f"[1:{naxis1}, 1:{naxis2}]", mode="fits").python
|
|
231
357
|
# Read auxiliary file if provided
|
|
232
358
|
if self.auxfile is not None:
|
|
359
|
+
# check if extension_auxfile is compatible with an integer
|
|
360
|
+
try:
|
|
361
|
+
extnum_aux = int(self.extension_auxfile)
|
|
362
|
+
self.extension_auxfile = extnum_aux
|
|
363
|
+
except ValueError:
|
|
364
|
+
# Keep as string (delaying checking until opening the file)
|
|
365
|
+
self.extension_auxfile = self.extension_auxfile.upper() # Convert to uppercase
|
|
233
366
|
try:
|
|
234
367
|
with fits.open(self.auxfile, mode="readonly") as hdul_aux:
|
|
368
|
+
if isinstance(self.extension_auxfile, int):
|
|
369
|
+
if self.extension_auxfile < 0 or self.extension_auxfile >= len(hdul_aux):
|
|
370
|
+
raise IndexError(f"Extension index {self.extension_auxfile} out of range.")
|
|
371
|
+
else:
|
|
372
|
+
if self.extension_auxfile not in hdul_aux:
|
|
373
|
+
raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
|
|
374
|
+
print(f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}")
|
|
235
375
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
236
376
|
if self.auxdata.shape != self.data.shape:
|
|
237
377
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -308,20 +448,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
308
448
|
self.button_frame1.pack(pady=5)
|
|
309
449
|
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
310
450
|
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
311
|
-
|
|
312
|
-
self.
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
self.overplot_cr_button = tk.Button(
|
|
317
|
-
self.button_frame1, text="CR overlay: Off", command=self.toggle_cr_overlay
|
|
318
|
-
)
|
|
319
|
-
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
320
|
-
self.apply_lacosmic_button = tk.Button(
|
|
451
|
+
self.load_detected_cr_button = tk.Button(
|
|
452
|
+
self.button_frame1, text="Load detected CRs", command=self.load_detected_cr_from_file
|
|
453
|
+
)
|
|
454
|
+
self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
455
|
+
self.replace_detected_cr_button = tk.Button(
|
|
321
456
|
self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
|
|
322
457
|
)
|
|
323
|
-
self.
|
|
324
|
-
self.
|
|
458
|
+
self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
459
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
325
460
|
self.examine_detected_cr_button = tk.Button(
|
|
326
461
|
self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
|
|
327
462
|
)
|
|
@@ -331,6 +466,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
331
466
|
# Row 2 of buttons
|
|
332
467
|
self.button_frame2 = tk.Frame(self.root)
|
|
333
468
|
self.button_frame2.pack(pady=5)
|
|
469
|
+
self.toggle_auxdata_button = tk.Button(self.button_frame2, text="[t]oggle data", command=self.toggle_auxdata)
|
|
470
|
+
self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
471
|
+
if self.auxdata is None:
|
|
472
|
+
self.toggle_auxdata_button.config(state=tk.DISABLED)
|
|
473
|
+
else:
|
|
474
|
+
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
475
|
+
self.image_aspect = "equal"
|
|
476
|
+
self.toggle_aspect_button = tk.Button(
|
|
477
|
+
self.button_frame2, text=f"[a]spect: {self.image_aspect}", command=self.toggle_aspect
|
|
478
|
+
)
|
|
479
|
+
self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
|
|
334
480
|
self.save_button = tk.Button(self.button_frame2, text="Save cleaned FITS", command=self.save_fits_file)
|
|
335
481
|
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
336
482
|
self.save_button.config(state=tk.DISABLED) # Initially disabled
|
|
@@ -340,6 +486,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
340
486
|
# Row 3 of buttons
|
|
341
487
|
self.button_frame3 = tk.Frame(self.root)
|
|
342
488
|
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)
|
|
343
492
|
vmin, vmax = zscale(self.data)
|
|
344
493
|
self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
|
|
345
494
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
@@ -349,6 +498,19 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
349
498
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
350
499
|
self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
|
|
351
500
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
501
|
+
if self.overplot_cr_pixels:
|
|
502
|
+
self.overplot_cr_button = tk.Button(
|
|
503
|
+
self.button_frame3,
|
|
504
|
+
text="CR overlay: ON ",
|
|
505
|
+
command=self.toggle_cr_overlay,
|
|
506
|
+
)
|
|
507
|
+
else:
|
|
508
|
+
self.overplot_cr_button = tk.Button(
|
|
509
|
+
self.button_frame3,
|
|
510
|
+
text="CR overlay: OFF",
|
|
511
|
+
command=self.toggle_cr_overlay,
|
|
512
|
+
)
|
|
513
|
+
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
352
514
|
|
|
353
515
|
# Figure
|
|
354
516
|
self.plot_frame = tk.Frame(self.root)
|
|
@@ -378,19 +540,63 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
378
540
|
xlabel = "X pixel (from 1 to NAXIS1)"
|
|
379
541
|
ylabel = "Y pixel (from 1 to NAXIS2)"
|
|
380
542
|
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
543
|
+
self.image_aspect = "equal"
|
|
544
|
+
self.displaying_auxdata = False
|
|
381
545
|
self.image, _, _ = imshow(
|
|
382
|
-
self.fig,
|
|
383
|
-
self.ax,
|
|
384
|
-
self.data,
|
|
546
|
+
fig=self.fig,
|
|
547
|
+
ax=self.ax,
|
|
548
|
+
data=self.data,
|
|
385
549
|
vmin=vmin,
|
|
386
550
|
vmax=vmax,
|
|
387
|
-
title=os.path.basename(self.input_fits),
|
|
551
|
+
title=f"data: {os.path.basename(self.input_fits)}",
|
|
388
552
|
xlabel=xlabel,
|
|
389
553
|
ylabel=ylabel,
|
|
390
554
|
extent=extent,
|
|
555
|
+
aspect=self.image_aspect,
|
|
391
556
|
)
|
|
392
557
|
self.fig.tight_layout()
|
|
393
558
|
|
|
559
|
+
def set_cursor_onoff(self):
|
|
560
|
+
"""Toggle cursor selection mode on or off."""
|
|
561
|
+
if not self.use_cursor:
|
|
562
|
+
self.use_cursor = True
|
|
563
|
+
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
564
|
+
else:
|
|
565
|
+
self.use_cursor = False
|
|
566
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
567
|
+
|
|
568
|
+
def toggle_auxdata(self):
|
|
569
|
+
"""Toggle between main data and auxiliary data for display."""
|
|
570
|
+
if self.displaying_auxdata:
|
|
571
|
+
# Switch to main data
|
|
572
|
+
vmin = self.get_vmin()
|
|
573
|
+
vmax = self.get_vmax()
|
|
574
|
+
self.image.set_data(self.data)
|
|
575
|
+
self.image.set_clim(vmin=vmin, vmax=vmax)
|
|
576
|
+
self.displaying_auxdata = False
|
|
577
|
+
self.ax.set_title(f"data: {os.path.basename(self.input_fits)}")
|
|
578
|
+
else:
|
|
579
|
+
# Switch to auxiliary data
|
|
580
|
+
vmin = self.get_vmin()
|
|
581
|
+
vmax = self.get_vmax()
|
|
582
|
+
self.image.set_data(self.auxdata)
|
|
583
|
+
self.image.set_clim(vmin=vmin, vmax=vmax)
|
|
584
|
+
self.displaying_auxdata = True
|
|
585
|
+
self.ax.set_title(f"auxdata: {os.path.basename(self.auxfile)}")
|
|
586
|
+
self.canvas.draw_idle()
|
|
587
|
+
|
|
588
|
+
def toggle_aspect(self):
|
|
589
|
+
"""Toggle the aspect ratio of the image display."""
|
|
590
|
+
if self.image_aspect == "equal":
|
|
591
|
+
self.image_aspect = "auto"
|
|
592
|
+
else:
|
|
593
|
+
self.image_aspect = "equal"
|
|
594
|
+
print(f"Setting image aspect to: {self.image_aspect}")
|
|
595
|
+
self.toggle_aspect_button.config(text=f"[a]spect: {self.image_aspect}")
|
|
596
|
+
self.ax.set_aspect(self.image_aspect)
|
|
597
|
+
self.fig.tight_layout()
|
|
598
|
+
self.canvas.draw_idle()
|
|
599
|
+
|
|
394
600
|
def run_lacosmic(self):
|
|
395
601
|
"""Run L.A.Cosmic to detect cosmic rays."""
|
|
396
602
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
@@ -427,17 +633,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
427
633
|
usefulmask[usefulregion] = 1.0
|
|
428
634
|
# Update parameter dictionary with new values
|
|
429
635
|
self.lacosmic_params = updated_params
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
636
|
+
if self.verbose:
|
|
637
|
+
print("Parameters updated:")
|
|
638
|
+
for key, info in self.lacosmic_params.items():
|
|
639
|
+
print(f" {key}: {info['value']}")
|
|
433
640
|
if self.lacosmic_params["nruns"]["value"] not in [1, 2]:
|
|
434
641
|
raise ValueError("nruns must be 1 or 2")
|
|
435
642
|
# Execute L.A.Cosmic with updated parameters
|
|
436
643
|
print("[bold green]Executing L.A.Cosmic (run 1)...[/bold green]")
|
|
437
644
|
borderpadd = updated_params["borderpadd"]["value"]
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
ccd=
|
|
645
|
+
cleandata_lacosmic, mask_crfound = lacosmicpad(
|
|
646
|
+
pad_width=borderpadd,
|
|
647
|
+
ccd=self.data,
|
|
441
648
|
gain=self.lacosmic_params["run1_gain"]["value"],
|
|
442
649
|
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
443
650
|
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
@@ -446,16 +653,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
446
653
|
niter=self.lacosmic_params["run1_niter"]["value"],
|
|
447
654
|
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
448
655
|
)
|
|
449
|
-
cleandata_lacosmic = cleandata_lacosmic[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
450
|
-
mask_crfound = mask_crfound[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
451
656
|
# Apply usefulmask to consider only selected region
|
|
452
657
|
cleandata_lacosmic *= usefulmask
|
|
453
658
|
mask_crfound = mask_crfound & (usefulmask.astype(bool))
|
|
454
659
|
# Second execution if nruns == 2
|
|
455
660
|
if self.lacosmic_params["nruns"]["value"] == 2:
|
|
456
661
|
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
457
|
-
cleandata_lacosmic2, mask_crfound2 =
|
|
458
|
-
|
|
662
|
+
cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
|
|
663
|
+
pad_width=borderpadd,
|
|
664
|
+
ccd=self.data,
|
|
459
665
|
gain=self.lacosmic_params["run2_gain"]["value"],
|
|
460
666
|
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
461
667
|
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
@@ -464,8 +670,6 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
464
670
|
niter=self.lacosmic_params["run2_niter"]["value"],
|
|
465
671
|
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
466
672
|
)
|
|
467
|
-
cleandata_lacosmic2 = cleandata_lacosmic2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
468
|
-
mask_crfound2 = mask_crfound2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
469
673
|
# Apply usefulmask to consider only selected region
|
|
470
674
|
cleandata_lacosmic2 *= usefulmask
|
|
471
675
|
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
@@ -473,19 +677,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
473
677
|
if np.any(mask_crfound):
|
|
474
678
|
print(f"Number of cosmic ray pixels (run1).......: {np.sum(mask_crfound)}")
|
|
475
679
|
print(f"Number of cosmic ray pixels (run2).......: {np.sum(mask_crfound2)}")
|
|
476
|
-
|
|
477
|
-
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
478
|
-
cr_labels2, num_features2 = ndimage.label(mask_crfound2, structure=structure)
|
|
479
|
-
# generate mask of ones at CR pixels found in first run
|
|
480
|
-
mask_peaks = np.zeros(mask_crfound.shape, dtype=float)
|
|
481
|
-
mask_peaks[mask_crfound] = 1.0
|
|
482
|
-
# preserve only those CR pixels in second run that are in the first run
|
|
483
|
-
cr_labels2_preserved = mask_peaks * cr_labels2
|
|
484
|
-
# generate new mask with preserved CR pixels from second run
|
|
485
|
-
mask_crfound = np.zeros_like(mask_crfound, dtype=bool)
|
|
486
|
-
for icr in np.unique(cr_labels2_preserved):
|
|
487
|
-
if icr > 0:
|
|
488
|
-
mask_crfound[cr_labels2 == icr] = True
|
|
680
|
+
mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2)
|
|
489
681
|
print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
|
|
490
682
|
# Use the cleandata from the second run
|
|
491
683
|
cleandata_lacosmic = cleandata_lacosmic2
|
|
@@ -495,42 +687,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
495
687
|
self.mask_crfound = np.zeros_like(self.data, dtype=bool)
|
|
496
688
|
self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
|
|
497
689
|
# Process the mask: dilation and labeling
|
|
498
|
-
|
|
499
|
-
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
500
|
-
dilation = self.lacosmic_params["dilation"]["value"]
|
|
501
|
-
if dilation > 0:
|
|
502
|
-
# Dilate the mask by the specified number of pixels
|
|
503
|
-
self.mask_crfound = dilatemask(
|
|
504
|
-
mask=self.mask_crfound, iterations=self.lacosmic_params["dilation"]["value"], connectivity=1
|
|
505
|
-
)
|
|
506
|
-
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
507
|
-
sdum = str(num_cr_pixels_after_dilation)
|
|
508
|
-
else:
|
|
509
|
-
sdum = str(num_cr_pixels_before_dilation)
|
|
510
|
-
print(
|
|
511
|
-
"Number of cosmic ray pixels detected by L.A.Cosmic: "
|
|
512
|
-
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
513
|
-
)
|
|
514
|
-
if dilation > 0:
|
|
515
|
-
print(
|
|
516
|
-
f"Number of cosmic ray pixels after dilation........: "
|
|
517
|
-
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
518
|
-
)
|
|
519
|
-
# Label connected components in the mask; note that by default,
|
|
520
|
-
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
521
|
-
# diagonal connections too, so we define a 3x3 square.
|
|
522
|
-
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
523
|
-
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
524
|
-
print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
|
|
525
|
-
self.apply_lacosmic_button.config(state=tk.NORMAL)
|
|
526
|
-
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
527
|
-
self.update_cr_overlay()
|
|
528
|
-
else:
|
|
529
|
-
print("No cosmic ray pixels detected by L.A.Cosmic.")
|
|
530
|
-
self.cr_labels = None
|
|
531
|
-
self.num_features = 0
|
|
532
|
-
self.apply_lacosmic_button.config(state=tk.DISABLED)
|
|
533
|
-
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
690
|
+
self.process_detected_cr(dilation=self.lacosmic_params["dilation"]["value"])
|
|
534
691
|
else:
|
|
535
692
|
print("Parameter editing cancelled. L.A.Cosmic detection skipped!")
|
|
536
693
|
self.run_lacosmic_button.config(state=tk.NORMAL)
|
|
@@ -539,9 +696,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
539
696
|
"""Toggle the overlay of cosmic ray pixels on the image."""
|
|
540
697
|
self.overplot_cr_pixels = not self.overplot_cr_pixels
|
|
541
698
|
if self.overplot_cr_pixels:
|
|
542
|
-
self.overplot_cr_button.config(text="CR overlay:
|
|
699
|
+
self.overplot_cr_button.config(text="CR overlay: ON ")
|
|
543
700
|
else:
|
|
544
|
-
self.overplot_cr_button.config(text="CR overlay:
|
|
701
|
+
self.overplot_cr_button.config(text="CR overlay: OFF")
|
|
545
702
|
self.update_cr_overlay()
|
|
546
703
|
|
|
547
704
|
def update_cr_overlay(self):
|
|
@@ -617,6 +774,21 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
617
774
|
# upate mask_crfound by eliminating the cleaned pixels
|
|
618
775
|
self.mask_crfound[mask_crfound_region] = False
|
|
619
776
|
data_has_been_modified = True
|
|
777
|
+
elif cleaning_method == "maskfill":
|
|
778
|
+
# Replace detected CR pixels with local median values
|
|
779
|
+
smoothed_output, _ = maskfill(
|
|
780
|
+
input_image=self.data,
|
|
781
|
+
mask=mask_crfound_region,
|
|
782
|
+
size=3,
|
|
783
|
+
operator="median",
|
|
784
|
+
smooth=True,
|
|
785
|
+
)
|
|
786
|
+
self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
|
|
787
|
+
# update mask_fixed to include the newly fixed pixels
|
|
788
|
+
self.mask_fixed[mask_crfound_region] = True
|
|
789
|
+
# upate mask_crfound by eliminating the cleaned pixels
|
|
790
|
+
self.mask_crfound[mask_crfound_region] = False
|
|
791
|
+
data_has_been_modified = True
|
|
620
792
|
elif cleaning_method == "auxdata":
|
|
621
793
|
if self.auxdata is None:
|
|
622
794
|
print("No auxiliary data available. Cleaning skipped!")
|
|
@@ -699,9 +871,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
699
871
|
# recalculate labels and number of features
|
|
700
872
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
701
873
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
702
|
-
|
|
874
|
+
num_cr_remaining = np.sum(self.mask_crfound)
|
|
875
|
+
sdum = str(num_cr_remaining)
|
|
703
876
|
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
704
877
|
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
878
|
+
if num_cr_remaining == 0:
|
|
879
|
+
self.use_cursor = False
|
|
880
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
705
881
|
# redraw image to show the changes
|
|
706
882
|
self.image.set_data(self.data)
|
|
707
883
|
self.canvas.draw_idle()
|
|
@@ -709,7 +885,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
709
885
|
self.save_button.config(state=tk.NORMAL)
|
|
710
886
|
if self.num_features == 0:
|
|
711
887
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
712
|
-
self.
|
|
888
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
713
889
|
self.update_cr_overlay()
|
|
714
890
|
|
|
715
891
|
def examine_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
|
|
@@ -765,9 +941,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
765
941
|
# recalculate labels and number of features
|
|
766
942
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
767
943
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
768
|
-
|
|
944
|
+
num_remaining = np.sum(self.mask_crfound)
|
|
945
|
+
sdum = str(num_remaining)
|
|
769
946
|
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
770
947
|
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
948
|
+
if num_remaining == 0:
|
|
949
|
+
self.use_cursor = False
|
|
950
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
771
951
|
# redraw image to show the changes
|
|
772
952
|
self.image.set_data(self.data)
|
|
773
953
|
self.canvas.draw_idle()
|
|
@@ -775,7 +955,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
775
955
|
self.save_button.config(state=tk.NORMAL)
|
|
776
956
|
if self.num_features == 0:
|
|
777
957
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
778
|
-
self.
|
|
958
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
779
959
|
self.update_cr_overlay()
|
|
780
960
|
|
|
781
961
|
def stop_app(self):
|
|
@@ -792,14 +972,39 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
792
972
|
|
|
793
973
|
def on_key(self, event):
|
|
794
974
|
"""Handle key press events."""
|
|
795
|
-
if event.key == "
|
|
796
|
-
|
|
975
|
+
if event.key == "c":
|
|
976
|
+
self.set_cursor_onoff()
|
|
977
|
+
elif event.key == "a":
|
|
978
|
+
self.toggle_aspect()
|
|
979
|
+
elif event.key == "t" and self.auxdata is not None:
|
|
980
|
+
self.toggle_auxdata()
|
|
797
981
|
elif event.key == ",":
|
|
798
982
|
self.set_minmax()
|
|
799
983
|
elif event.key == "/":
|
|
800
984
|
self.set_zscale()
|
|
801
|
-
|
|
802
|
-
|
|
985
|
+
elif event.key == "o":
|
|
986
|
+
self.toolbar.zoom()
|
|
987
|
+
elif event.key == "h":
|
|
988
|
+
self.toolbar.home()
|
|
989
|
+
elif event.key == "p":
|
|
990
|
+
self.toolbar.pan()
|
|
991
|
+
elif event.key == "s":
|
|
992
|
+
self.toolbar.save_figure()
|
|
993
|
+
elif event.key == "?":
|
|
994
|
+
# Display list of keyboard shortcuts
|
|
995
|
+
print("[bold blue]Keyboard Shortcuts:[/bold blue]")
|
|
996
|
+
print("[red] c [/red]: Toggle cursor selection mode on/off")
|
|
997
|
+
print("[red] t [/red]: Toggle between main data and auxiliary data")
|
|
998
|
+
print("[red] a [/red]: Toggle image aspect ratio equal/auto")
|
|
999
|
+
print("[red] , [/red]: Set vmin and vmax to minmax")
|
|
1000
|
+
print("[red] / [/red]: Set vmin and vmax using zscale")
|
|
1001
|
+
print("[red] h [/red]: Go to home view \\[toolbar]")
|
|
1002
|
+
print("[red] o [/red]: Activate zoom mode \\[toolbar]")
|
|
1003
|
+
print("[red] p [/red]: Activate pan mode \\[toolbar]")
|
|
1004
|
+
print("[red] s [/red]: Save the current figure \\[toolbar]")
|
|
1005
|
+
print("[red] q [/red]: (ignored) prevent closing the window")
|
|
1006
|
+
elif event.key == "q":
|
|
1007
|
+
pass # Ignore the "q" key to prevent closing the window
|
|
803
1008
|
|
|
804
1009
|
def on_click(self, event):
|
|
805
1010
|
"""Handle mouse click events on the image."""
|
|
@@ -814,6 +1019,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
814
1019
|
print(f"Toolbar mode '{toolbar.mode}' active; click ignored.")
|
|
815
1020
|
return
|
|
816
1021
|
|
|
1022
|
+
# proceed only if cursor selection mode is on
|
|
1023
|
+
if not self.use_cursor:
|
|
1024
|
+
return
|
|
1025
|
+
|
|
817
1026
|
# ignore clicks outside the expected axes
|
|
818
1027
|
# (note that the color bar is a different axes)
|
|
819
1028
|
if event.inaxes == self.ax:
|
|
@@ -43,7 +43,7 @@ lacosmic_default_dict = {
|
|
|
43
43
|
}
|
|
44
44
|
|
|
45
45
|
# Default parameters for cleaning methods
|
|
46
|
-
VALID_CLEANING_METHODS = ["x interp.", "y interp.", "surface interp.", "median", "mean", "lacosmic", "auxdata"]
|
|
46
|
+
VALID_CLEANING_METHODS = ["x interp.", "y interp.", "surface interp.", "median", "mean", "lacosmic", "maskfill", "auxdata"]
|
|
47
47
|
|
|
48
48
|
# Maximum pixel distance to consider when finding closest CR pixel
|
|
49
49
|
MAX_PIXEL_DISTANCE_TO_CR = 15
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
from scipy import ndimage
|
|
13
13
|
import numpy as np
|
|
14
|
+
from tqdm import tqdm
|
|
14
15
|
|
|
15
16
|
from .dilatemask import dilatemask
|
|
16
17
|
from .interpolation_x import interpolation_x
|
|
@@ -18,7 +19,7 @@ from .interpolation_y import interpolation_y
|
|
|
18
19
|
from .interpolation_a import interpolation_a
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
+
def interpolate(data, mask_crfound, dilation=0, interp_method=None, npoints=None, degree=None, debug=False):
|
|
22
23
|
"""Interpolate pixels identified in a mask.
|
|
23
24
|
|
|
24
25
|
The original data and mask are not modified. A copy of both
|
|
@@ -29,8 +30,8 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
|
|
|
29
30
|
data : 2D numpy.ndarray
|
|
30
31
|
The image data array to be processed.
|
|
31
32
|
mask_crfound : 2D numpy.ndarray of bool
|
|
32
|
-
A boolean mask array indicating which pixels are
|
|
33
|
-
|
|
33
|
+
A boolean mask array indicating which pixels are flagged
|
|
34
|
+
and need to be interpolated (True = pixel to be fixed).
|
|
34
35
|
dilation : int, optional
|
|
35
36
|
The number of pixels to dilate the masked pixels before
|
|
36
37
|
interpolation.
|
|
@@ -48,7 +49,7 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
|
|
|
48
49
|
The degree of the polynomial to fit. This parameter is
|
|
49
50
|
relevant for 'x' and 'y' methods.
|
|
50
51
|
debug : bool, optional
|
|
51
|
-
If True, print debug information.
|
|
52
|
+
If True, print debug information and enable tqdm progress bar.
|
|
52
53
|
|
|
53
54
|
Returns
|
|
54
55
|
-------
|
|
@@ -86,13 +87,14 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
|
|
|
86
87
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
87
88
|
cr_labels, num_features = ndimage.label(updated_mask_crfound, structure=structure)
|
|
88
89
|
if debug:
|
|
89
|
-
|
|
90
|
-
print(f"Number of cosmic
|
|
90
|
+
sdum = str(np.sum(updated_mask_crfound))
|
|
91
|
+
print(f"Number of cosmic ray pixels to be cleaned: {sdum}")
|
|
92
|
+
print(f"Number of cosmic rays (grouped pixels)...: {num_features:>{len(sdum)}}")
|
|
91
93
|
|
|
92
94
|
# Fix cosmic rays using the specified interpolation method
|
|
93
95
|
cleaned_data = data.copy()
|
|
94
96
|
num_cr_cleaned = 0
|
|
95
|
-
for cr_index in range(1, num_features + 1):
|
|
97
|
+
for cr_index in tqdm(range(1, num_features + 1), disable=not debug):
|
|
96
98
|
if interp_method in ["x", "y"]:
|
|
97
99
|
if 2 * npoints <= degree:
|
|
98
100
|
raise ValueError("2*npoints must be greater than degree for polynomial interpolation.")
|
|
@@ -131,6 +133,6 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
|
|
|
131
133
|
raise ValueError(f"Unknown interpolation method: {interp_method}")
|
|
132
134
|
|
|
133
135
|
if debug:
|
|
134
|
-
print(f"Number of cosmic rays cleaned............: {num_cr_cleaned}")
|
|
136
|
+
print(f"Number of cosmic rays cleaned............: {num_cr_cleaned:>{len(sdum)}}")
|
|
135
137
|
|
|
136
138
|
return cleaned_data, mask_fixed
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0+
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Execute LACosmic algorithm on a padded image."""
|
|
11
|
+
|
|
12
|
+
from ccdproc import cosmicray_lacosmic
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lacosmicpad(pad_width, **kwargs):
|
|
17
|
+
"""Execute LACosmic algorithm on a padded array.
|
|
18
|
+
|
|
19
|
+
This function pads the input image array before applying the LACosmic
|
|
20
|
+
cosmic ray cleaning algorithm. After processing, the padding is removed
|
|
21
|
+
to return an array of the original size.
|
|
22
|
+
|
|
23
|
+
The padding helps to mitigate edge effects that can occur during the
|
|
24
|
+
cosmic ray detection and cleaning process.
|
|
25
|
+
|
|
26
|
+
Apart from the `pad_width` parameter, all other keyword arguments
|
|
27
|
+
are passed directly to the `cosmicray_lacosmic` function from the
|
|
28
|
+
`ccdproc` package.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
pad_width : int
|
|
33
|
+
Width of the padding to be applied to the image before executing
|
|
34
|
+
the LACosmic algorithm.
|
|
35
|
+
**kwargs : dict
|
|
36
|
+
Keyword arguments to be passed to the `cosmicray_lacosmic` function.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
clean_array : 2D numpy.ndarray
|
|
41
|
+
The cleaned image array after applying the LACosmic algorithm with padding.
|
|
42
|
+
mask_array : 2D numpy.ndarray of bool
|
|
43
|
+
The mask array indicating detected cosmic rays.
|
|
44
|
+
"""
|
|
45
|
+
if "ccd" not in kwargs:
|
|
46
|
+
raise ValueError("The 'ccd' keyword argument must be provided.")
|
|
47
|
+
array = kwargs.pop("ccd")
|
|
48
|
+
if not isinstance(array, np.ndarray):
|
|
49
|
+
raise TypeError("The 'ccd' keyword argument must be a numpy ndarray.")
|
|
50
|
+
# Pad the array
|
|
51
|
+
padded_array = np.pad(array, pad_width, mode="reflect")
|
|
52
|
+
# Apply LACosmic algorithm to the padded array
|
|
53
|
+
cleaned_padded_array, mask_padded_array = cosmicray_lacosmic(ccd=padded_array, **kwargs)
|
|
54
|
+
# Remove padding
|
|
55
|
+
cleaned_array = cleaned_padded_array[pad_width:-pad_width, pad_width:-pad_width]
|
|
56
|
+
mask_array = mask_padded_array[pad_width:-pad_width, pad_width:-pad_width]
|
|
57
|
+
return cleaned_array, mask_array
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0+
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Merge peak and tail masks for cosmic ray cleaning."""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from scipy import ndimage
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def merge_peak_tail_masks(mask_peaks, mask_tails):
|
|
17
|
+
"""Merge peak and tail masks for cosmic ray cleaning.
|
|
18
|
+
|
|
19
|
+
Tail pixels are preserved only if they correspond to CR features
|
|
20
|
+
that are also present in the peak mask.
|
|
21
|
+
|
|
22
|
+
Parameters
|
|
23
|
+
----------
|
|
24
|
+
mask_peaks : ndarray
|
|
25
|
+
Boolean array indicating the pixels identified as cosmic ray peaks.
|
|
26
|
+
mask_tails : ndarray
|
|
27
|
+
Boolean array indicating the pixels identified as cosmic ray tails.
|
|
28
|
+
|
|
29
|
+
Returns
|
|
30
|
+
-------
|
|
31
|
+
merged_mask : ndarray
|
|
32
|
+
Boolean array indicating the merged cosmic ray mask.
|
|
33
|
+
"""
|
|
34
|
+
# check that input masks are numpy arrays
|
|
35
|
+
if not isinstance(mask_peaks, np.ndarray) or not isinstance(mask_tails, np.ndarray):
|
|
36
|
+
raise TypeError("Input masks must be numpy arrays.")
|
|
37
|
+
# check that input masks have the same shape
|
|
38
|
+
if mask_peaks.shape != mask_tails.shape:
|
|
39
|
+
raise ValueError("Input masks must have the same shape.")
|
|
40
|
+
# check that input masks are boolean arrays
|
|
41
|
+
if mask_peaks.dtype != bool or mask_tails.dtype != bool:
|
|
42
|
+
raise TypeError("Input masks must be boolean arrays.")
|
|
43
|
+
|
|
44
|
+
# find structures in tail mask
|
|
45
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
46
|
+
cr_labels_tails, num_crs_tails = ndimage.label(mask_tails, structure=structure)
|
|
47
|
+
# generate mask of ones at peak pixels
|
|
48
|
+
mask_peaks_ones = np.zeros(mask_peaks.shape, dtype=float)
|
|
49
|
+
mask_peaks_ones[mask_peaks] = 1.0
|
|
50
|
+
# preserve only those tail pixels that are flagged as peaks
|
|
51
|
+
cr_labels_tails_preserved = mask_peaks_ones * cr_labels_tails
|
|
52
|
+
# generate new mask with preserved tail pixels
|
|
53
|
+
merged_mask = np.zeros_like(mask_peaks, dtype=bool)
|
|
54
|
+
for icr in np.unique(cr_labels_tails_preserved):
|
|
55
|
+
if icr > 0:
|
|
56
|
+
merged_mask[cr_labels_tails == icr] = True
|
|
57
|
+
|
|
58
|
+
return merged_mask
|
|
@@ -116,11 +116,11 @@ class ModalProgressBar:
|
|
|
116
116
|
|
|
117
117
|
elapsed_str = self._format_time(elapsed)
|
|
118
118
|
eta_str = self._format_time(eta_seconds)
|
|
119
|
+
total_str = self._format_time(elapsed + eta_seconds)
|
|
119
120
|
rate_str = f"{rate:.2f} CR/s" if rate >= 1 else f"{1/rate:.2f} s/CR"
|
|
120
121
|
|
|
121
122
|
self.status_label.config(text=f"{self.current}/{self.total} ({percentage:.1f}%) | {rate_str}")
|
|
122
|
-
self.time_label.config(text=f"Elapsed: {elapsed_str} | ETA: {eta_str}")
|
|
123
|
-
|
|
123
|
+
self.time_label.config(text=f"Expected Total: {total_str} | Elapsed: {elapsed_str} | ETA: {eta_str}")
|
|
124
124
|
self.window.update_idletasks()
|
|
125
125
|
self.window.update()
|
|
126
126
|
|
|
@@ -101,10 +101,21 @@ class ParameterEditor:
|
|
|
101
101
|
bold_font = default_font.copy()
|
|
102
102
|
bold_font.configure(weight="bold", size=default_font.cget("size") + 2)
|
|
103
103
|
subtitle_label = tk.Label(main_frame, text="L.A.Cosmic Parameters", font=bold_font)
|
|
104
|
-
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0,
|
|
104
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
|
|
105
105
|
row += 1
|
|
106
106
|
|
|
107
107
|
# Create labels and entry fields for each parameter.
|
|
108
|
+
bold_font_subheader = default_font.copy()
|
|
109
|
+
bold_font_subheader.configure(weight="bold", size=default_font.cget("size") + 1)
|
|
110
|
+
label = tk.Label(main_frame, text="Parameter", font=bold_font_subheader, anchor="w", fg="gray")
|
|
111
|
+
label.grid(row=row, column=0, sticky="e", pady=0)
|
|
112
|
+
label = tk.Label(main_frame, text="Run 1", font=bold_font_subheader, anchor="w", fg="gray", width=10)
|
|
113
|
+
label.grid(row=row, column=1, sticky="w", padx=10, pady=0)
|
|
114
|
+
label = tk.Label(main_frame, text="Run 2", font=bold_font_subheader, anchor="w", fg="gray", width=10)
|
|
115
|
+
label.grid(row=row, column=2, sticky="w", padx=10, pady=0)
|
|
116
|
+
label = tk.Label(main_frame, text="Type", font=bold_font_subheader, anchor="w", fg="gray", width=10)
|
|
117
|
+
label.grid(row=row, column=3, sticky="w", pady=0)
|
|
118
|
+
row += 1
|
|
108
119
|
# Note: here we are using entry_vars to trace changes in the entries
|
|
109
120
|
# so that we can update the color of run2 entries if they differ from run1.
|
|
110
121
|
self.entry_vars = {}
|
|
@@ -142,7 +153,7 @@ class ParameterEditor:
|
|
|
142
153
|
|
|
143
154
|
# Subtitle for additional parameters
|
|
144
155
|
subtitle_label = tk.Label(main_frame, text="Additional Parameters", font=bold_font)
|
|
145
|
-
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0,
|
|
156
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
|
|
146
157
|
row += 1
|
|
147
158
|
|
|
148
159
|
# Dilation label and entry
|
|
@@ -177,7 +188,7 @@ class ParameterEditor:
|
|
|
177
188
|
|
|
178
189
|
# Subtitle for region to be examined
|
|
179
190
|
subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=bold_font)
|
|
180
|
-
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0,
|
|
191
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
|
|
181
192
|
row += 1
|
|
182
193
|
|
|
183
194
|
# Region to be examined label and entries
|
|
@@ -13,6 +13,7 @@ import tkinter as tk
|
|
|
13
13
|
from tkinter import messagebox
|
|
14
14
|
from tkinter import simpledialog
|
|
15
15
|
|
|
16
|
+
from maskfill import maskfill
|
|
16
17
|
import matplotlib.pyplot as plt
|
|
17
18
|
from matplotlib.backend_bases import key_press_handler
|
|
18
19
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
@@ -150,7 +151,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
150
151
|
self.root.minsize(1000, 700)
|
|
151
152
|
else:
|
|
152
153
|
# self.root.geometry("800x700+100+100") # This does not work in Fedora
|
|
153
|
-
self.root.minsize(
|
|
154
|
+
self.root.minsize(900, 700)
|
|
154
155
|
self.root.update_idletasks()
|
|
155
156
|
self.root.geometry("+100+100")
|
|
156
157
|
self.data = data
|
|
@@ -224,6 +225,8 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
224
225
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
225
226
|
if self.cleandata_lacosmic is None:
|
|
226
227
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
228
|
+
self.interp_maskfill_button = tk.Button(self.button_frame2, text="mas[k]fill", command=self.use_maskfill)
|
|
229
|
+
self.interp_maskfill_button.pack(side=tk.LEFT, padx=5)
|
|
227
230
|
self.interp_aux_button = tk.Button(self.button_frame2, text="[a]ux. data", command=self.use_auxdata)
|
|
228
231
|
self.interp_aux_button.pack(side=tk.LEFT, padx=5)
|
|
229
232
|
if self.auxdata is None:
|
|
@@ -246,7 +249,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
246
249
|
if self.auxdata is not None:
|
|
247
250
|
self.fig, (self.ax_aux, self.ax) = plt.subplots(ncols=2, figsize=(11, 5.5), constrained_layout=True)
|
|
248
251
|
else:
|
|
249
|
-
self.fig, self.ax = plt.subplots(figsize=(
|
|
252
|
+
self.fig, self.ax = plt.subplots(figsize=(9, 5.5), constrained_layout=True)
|
|
250
253
|
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
|
|
251
254
|
self.canvas.get_tk_widget().pack(padx=5, pady=5)
|
|
252
255
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
@@ -376,6 +379,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
376
379
|
self.interp_d_button.config(state=tk.DISABLED)
|
|
377
380
|
self.interp_m_button.config(state=tk.DISABLED)
|
|
378
381
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
382
|
+
self.interp_maskfill_button.config(state=tk.DISABLED)
|
|
379
383
|
self.interp_aux_button.config(state=tk.DISABLED)
|
|
380
384
|
|
|
381
385
|
def interp_x(self):
|
|
@@ -461,6 +465,27 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
461
465
|
self.set_buttons_after_cleaning_cr()
|
|
462
466
|
self.update_display(cleaned=True)
|
|
463
467
|
|
|
468
|
+
def use_maskfill(self):
|
|
469
|
+
"""Use maskfill cleaned data to clean a cosmic ray."""
|
|
470
|
+
print(f"Maskfill interpolation of cosmic ray {self.cr_index}")
|
|
471
|
+
ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
|
|
472
|
+
mask = np.zeros(self.data.shape, dtype=bool)
|
|
473
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
474
|
+
mask[iy, ix] = True
|
|
475
|
+
smoothed_output, _ = maskfill(
|
|
476
|
+
input_image=self.data,
|
|
477
|
+
mask=mask,
|
|
478
|
+
size=3,
|
|
479
|
+
operator="median",
|
|
480
|
+
smooth=True,
|
|
481
|
+
)
|
|
482
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
483
|
+
self.data[iy, ix] = smoothed_output[iy, ix]
|
|
484
|
+
self.mask_fixed[iy, ix] = True
|
|
485
|
+
self.num_cr_cleaned += 1
|
|
486
|
+
self.set_buttons_after_cleaning_cr()
|
|
487
|
+
self.update_display(cleaned=True)
|
|
488
|
+
|
|
464
489
|
def use_auxdata(self):
|
|
465
490
|
"""Use auxiliary data to clean a cosmic ray."""
|
|
466
491
|
if self.auxdata is None:
|
|
@@ -505,6 +530,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
505
530
|
if self.cleandata_lacosmic is not None:
|
|
506
531
|
if self.last_dilation is None or self.last_dilation == 0:
|
|
507
532
|
self.interp_l_button.config(state=tk.NORMAL)
|
|
533
|
+
self.interp_maskfill_button.config(state=tk.NORMAL)
|
|
508
534
|
if self.auxdata is not None:
|
|
509
535
|
self.interp_aux_button.config(state=tk.NORMAL)
|
|
510
536
|
print(f"Restored all pixels of cosmic ray {self.cr_index}")
|
|
@@ -566,6 +592,9 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
566
592
|
elif event.key == "l":
|
|
567
593
|
if self.interp_l_button.cget("state") != "disabled":
|
|
568
594
|
self.use_lacosmic()
|
|
595
|
+
elif event.key == "k":
|
|
596
|
+
if self.interp_maskfill_button.cget("state") != "disabled":
|
|
597
|
+
self.use_maskfill()
|
|
569
598
|
elif event.key == "a":
|
|
570
599
|
if self.interp_aux_button.cget("state") != "disabled":
|
|
571
600
|
self.use_auxdata()
|
teareduce/tests/test_cleanest.py
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
import numpy as np
|
|
13
13
|
|
|
14
14
|
|
|
15
|
-
from ..cleanest.
|
|
15
|
+
from ..cleanest.interpolate import interpolate
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
def test_cleanest_no_cr():
|
|
@@ -23,7 +23,7 @@ def test_cleanest_no_cr():
|
|
|
23
23
|
mask_crfound = np.zeros_like(data, dtype=bool)
|
|
24
24
|
|
|
25
25
|
for interp_method in ['x', 'y', 's', 'd', 'm']:
|
|
26
|
-
cleaned_data, mask_fixed =
|
|
26
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound, interp_method=interp_method, npoints=2, degree=1)
|
|
27
27
|
|
|
28
28
|
assert np.array_equal(cleaned_data, data), "Data should remain unchanged when no cosmic rays are present."
|
|
29
29
|
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged when no cosmic rays are present."
|
|
@@ -38,7 +38,7 @@ def test_cleanest_interpolation_x():
|
|
|
38
38
|
[False, False, True, False, False],
|
|
39
39
|
[False, False, True, False, False]], dtype=bool)
|
|
40
40
|
|
|
41
|
-
cleaned_data, mask_fixed =
|
|
41
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound,
|
|
42
42
|
interp_method='x', npoints=2, degree=1)
|
|
43
43
|
|
|
44
44
|
expected_data = np.array([[1, 1, 1, 1, 1],
|
|
@@ -62,8 +62,8 @@ def test_cleanest_interpolation_y():
|
|
|
62
62
|
[False, False, False],
|
|
63
63
|
[False, False, False]], dtype=bool)
|
|
64
64
|
|
|
65
|
-
cleaned_data, mask_fixed =
|
|
66
|
-
|
|
65
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound,
|
|
66
|
+
interp_method='y', npoints=2, degree=1)
|
|
67
67
|
|
|
68
68
|
expected_data = np.array([[1, 2, 3],
|
|
69
69
|
[1, 2, 3],
|
|
@@ -83,8 +83,8 @@ def test_cleanest_interpolation_surface():
|
|
|
83
83
|
[False, True, False],
|
|
84
84
|
[False, False, False]], dtype=bool)
|
|
85
85
|
|
|
86
|
-
cleaned_data, mask_fixed =
|
|
87
|
-
|
|
86
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound,
|
|
87
|
+
interp_method='s', npoints=1)
|
|
88
88
|
|
|
89
89
|
expected_data = np.array([[1, 2, 3],
|
|
90
90
|
[4, 5, 6],
|
|
@@ -102,8 +102,8 @@ def test_cleanest_interpolation_median():
|
|
|
102
102
|
[False, True, False],
|
|
103
103
|
[False, False, False]], dtype=bool)
|
|
104
104
|
|
|
105
|
-
cleaned_data, mask_fixed =
|
|
106
|
-
|
|
105
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound,
|
|
106
|
+
interp_method='d', npoints=1)
|
|
107
107
|
|
|
108
108
|
expected_data = np.array([[1, 2, 3],
|
|
109
109
|
[4, 5, 6],
|
|
@@ -121,8 +121,8 @@ def test_cleanest_interpolation_mean():
|
|
|
121
121
|
[False, True, False],
|
|
122
122
|
[False, False, False]], dtype=bool)
|
|
123
123
|
|
|
124
|
-
cleaned_data, mask_fixed =
|
|
125
|
-
|
|
124
|
+
cleaned_data, mask_fixed = interpolate(data, mask_crfound,
|
|
125
|
+
interp_method='m', npoints=1)
|
|
126
126
|
|
|
127
127
|
expected_data = np.array([[1, 2, 3],
|
|
128
128
|
[4, 5, 6],
|
teareduce/version.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: teareduce
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.8
|
|
4
4
|
Summary: Utilities for astronomical data reduction
|
|
5
5
|
Author-email: Nicolás Cardiel <cardiel@ucm.es>
|
|
6
6
|
License: GPL-3.0-or-later
|
|
@@ -23,6 +23,7 @@ Requires-Dist: astropy
|
|
|
23
23
|
Requires-Dist: ccdproc
|
|
24
24
|
Requires-Dist: lmfit
|
|
25
25
|
Requires-Dist: matplotlib
|
|
26
|
+
Requires-Dist: maskfill
|
|
26
27
|
Requires-Dist: numpy>=1.22
|
|
27
28
|
Requires-Dist: requests
|
|
28
29
|
Requires-Dist: rich
|
|
@@ -15,35 +15,37 @@ teareduce/sdistortion.py,sha256=5ZsZn4vD5Sw2aoqO8-NIOH7H89Zmh7ZDkow6YbAotHU,5916
|
|
|
15
15
|
teareduce/simulateccdexposure.py,sha256=cdbpca6GVuM3d7R1LGzlIZZvjTq_jzrlkk_Cli7aouQ,24636
|
|
16
16
|
teareduce/sliceregion.py,sha256=Jdf8XvmGaY_vaY1cneTaRtSOYPxpUsJm9cXJDDMa0YM,18626
|
|
17
17
|
teareduce/statsummary.py,sha256=VTNAnBV8z6suqiyB2Lhw3YjUUOjlmwUPX3enkOKRF54,5422
|
|
18
|
-
teareduce/version.py,sha256=
|
|
18
|
+
teareduce/version.py,sha256=G40b1Oo_cwqV4sHca8L26xY3VpGjlKvOFDTnZdytrtg,419
|
|
19
19
|
teareduce/wavecal.py,sha256=2MewWz5Y3ms41c305UtWOM_LaLNdk1mugDXCtzf-fSw,68586
|
|
20
20
|
teareduce/write_array_to_fits.py,sha256=kWDrEH9coJ1yIu56oQJpWtDqJL4c8HGmssE9jle4e94,617
|
|
21
21
|
teareduce/zscale.py,sha256=SDgmcDD2N5GvDn46JADCgTQJPBF_N_jSKMHoeTz9Nsw,1152
|
|
22
|
-
teareduce/cleanest/__init__.py,sha256=
|
|
23
|
-
teareduce/cleanest/__main__.py,sha256=
|
|
22
|
+
teareduce/cleanest/__init__.py,sha256=hHFtV6uBx6HAI8LFUDli0urznTUzMotCW9J_4EKGCXc,293
|
|
23
|
+
teareduce/cleanest/__main__.py,sha256=cMzvNWNe2fuTGHLtT3hOc9-NAcMXDZUqHN0SSHWWDnc,5977
|
|
24
24
|
teareduce/cleanest/centerchildparent.py,sha256=wHxOvNrrQ-KBLZAbtQ9bJAxYhGOzqYBF4LgdIQk7UF8,1285
|
|
25
|
-
teareduce/cleanest/
|
|
26
|
-
teareduce/cleanest/
|
|
27
|
-
teareduce/cleanest/definitions.py,sha256=HLv41cuhUOUyEicRHea0LRTv4Wagm24miSxFt4cZN3g,2546
|
|
25
|
+
teareduce/cleanest/cosmicraycleanerapp.py,sha256=gugC79tdQFhT7plOWKqm5uTtjJCiWIeDpNmaCiE7aA4,50584
|
|
26
|
+
teareduce/cleanest/definitions.py,sha256=L7VXT0SAjv96zgZ_VhMMV1vcf-oxdwefvlmPclBA-ug,2558
|
|
28
27
|
teareduce/cleanest/dilatemask.py,sha256=I5tHAv5VEO6V0Wed8Ar20uLt4F9P-tgjmLL5BAaFvgM,1276
|
|
29
28
|
teareduce/cleanest/find_closest_true.py,sha256=mWdIvhipzAXDRKfePDrP7f0lP4U48cckpHiKwiB4jHI,1320
|
|
30
29
|
teareduce/cleanest/imagedisplay.py,sha256=820Vn-Q0bJyHicOBsxDmfAZxuGOFepEEsm0LTxlPJjc,5848
|
|
30
|
+
teareduce/cleanest/interpolate.py,sha256=qlz4SHw89ahBRx1VHBMkd0IEpFkd96P6FVfsw_LoQDA,5194
|
|
31
31
|
teareduce/cleanest/interpolation_a.py,sha256=zE4VIrC41vapf0Vx9qmh1oacw2qkJwcuMnV3JpSDW8Y,4007
|
|
32
32
|
teareduce/cleanest/interpolation_x.py,sha256=D5hKbobT6lJk18XktP3PXhzEN12zqed6U18yfQ-reLQ,3744
|
|
33
33
|
teareduce/cleanest/interpolation_y.py,sha256=O6yw5nKKlTdV6qsP1Ku6CwGhXB6o3j0_YQirXyILi8c,3759
|
|
34
|
-
teareduce/cleanest/interpolationeditor.py,sha256=
|
|
35
|
-
teareduce/cleanest/
|
|
36
|
-
teareduce/cleanest/
|
|
37
|
-
teareduce/cleanest/
|
|
34
|
+
teareduce/cleanest/interpolationeditor.py,sha256=J5vrioLTwUSMbUeY0ezqNqm3CvLJgKFe7gvZ44Z62sc,13257
|
|
35
|
+
teareduce/cleanest/lacosmicpad.py,sha256=xWaxifoF_B82Gm5czuJmL7Y4Cv0W___LlvcpH633bAo,2047
|
|
36
|
+
teareduce/cleanest/mergemasks.py,sha256=1Vq6Wqn6DClxSAvHwy6QrJGszA8nkmoMHITyprSykHI,2072
|
|
37
|
+
teareduce/cleanest/modalprogressbar.py,sha256=uwd-p92PvOVJnbXd-B8DRcBZ--keKpr4ZN9PLeqm1Ws,6449
|
|
38
|
+
teareduce/cleanest/parametereditor.py,sha256=_0baFbkIUcPyF-teFIRMt1MpA4IzDZc7VHNh1h0qN4A,14229
|
|
39
|
+
teareduce/cleanest/reviewcosmicray.py,sha256=yTY9vNyYSuh_AZsIJAEtJ50mgIKhjgTe-LQob4xQx9U,29497
|
|
38
40
|
teareduce/cookbook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
39
41
|
teareduce/cookbook/get_cookbook_file.py,sha256=vde-iNii2lm1QII8GmLRsFsKNxkdsd7njCBE-8Z7io0,1088
|
|
40
42
|
teareduce/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
41
|
-
teareduce/tests/test_cleanest.py,sha256=
|
|
43
|
+
teareduce/tests/test_cleanest.py,sha256=6dRqkw1RQMKsFrC8cEweMvTD6wXhiDv3P4PS57-HEqI,5598
|
|
42
44
|
teareduce/tests/test_sliceregion.py,sha256=S7Zoh2eEBFIEbfsXgWBEMCf7pottjw2oLhqlZJQkAwg,3785
|
|
43
45
|
teareduce/tests/test_version.py,sha256=mKLnbXyvVNc1pATq5PxR8qeoFMPAFL_GilFV6IHLOi0,172
|
|
44
|
-
teareduce-0.5.
|
|
45
|
-
teareduce-0.5.
|
|
46
|
-
teareduce-0.5.
|
|
47
|
-
teareduce-0.5.
|
|
48
|
-
teareduce-0.5.
|
|
49
|
-
teareduce-0.5.
|
|
46
|
+
teareduce-0.5.8.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
47
|
+
teareduce-0.5.8.dist-info/METADATA,sha256=Xm0x4wiGEoHvS_3tIk7gmaLHZbN0HDuN_Iec_wdQg84,3137
|
|
48
|
+
teareduce-0.5.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
teareduce-0.5.8.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
|
|
50
|
+
teareduce-0.5.8.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
|
|
51
|
+
teareduce-0.5.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|