teareduce 0.5.5__py3-none-any.whl → 0.5.7__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 +287 -94
- teareduce/cleanest/{cleanest.py → interpolate.py} +10 -8
- 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/tests/test_cleanest.py +11 -11
- teareduce/version.py +1 -1
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.dist-info}/METADATA +1 -1
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.dist-info}/RECORD +16 -14
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.dist-info}/WHEEL +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.5.dist-info → teareduce-0.5.7.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,17 @@ 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 ccdproc import cosmicray_lacosmic
|
|
20
20
|
import matplotlib.pyplot as plt
|
|
21
21
|
from matplotlib.backend_bases import key_press_handler
|
|
22
22
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
23
23
|
from scipy import ndimage
|
|
24
24
|
import numpy as np
|
|
25
25
|
import os
|
|
26
|
+
from pathlib import Path
|
|
26
27
|
from rich import print
|
|
27
28
|
|
|
28
29
|
from .centerchildparent import center_on_parent
|
|
@@ -41,6 +42,8 @@ from .interpolation_x import interpolation_x
|
|
|
41
42
|
from .interpolation_y import interpolation_y
|
|
42
43
|
from .interpolationeditor import InterpolationEditor
|
|
43
44
|
from .imagedisplay import ImageDisplay
|
|
45
|
+
from .lacosmicpad import lacosmicpad
|
|
46
|
+
from .mergemasks import merge_peak_tail_masks
|
|
44
47
|
from .parametereditor import ParameterEditor
|
|
45
48
|
from .reviewcosmicray import ReviewCosmicRay
|
|
46
49
|
from .modalprogressbar import ModalProgressBar
|
|
@@ -61,13 +64,14 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
61
64
|
self,
|
|
62
65
|
root,
|
|
63
66
|
input_fits,
|
|
64
|
-
extension=0,
|
|
67
|
+
extension="0",
|
|
65
68
|
auxfile=None,
|
|
66
|
-
extension_auxfile=0,
|
|
69
|
+
extension_auxfile="0",
|
|
67
70
|
fontfamily=DEFAULT_FONT_FAMILY,
|
|
68
71
|
fontsize=DEFAULT_FONT_SIZE,
|
|
69
72
|
width=DEFAULT_TK_WINDOW_SIZE_X,
|
|
70
73
|
height=DEFAULT_TK_WINDOW_SIZE_Y,
|
|
74
|
+
verbose=False,
|
|
71
75
|
):
|
|
72
76
|
"""
|
|
73
77
|
Initialize the application.
|
|
@@ -78,19 +82,29 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
78
82
|
The main Tkinter window.
|
|
79
83
|
input_fits : str
|
|
80
84
|
Path to the FITS file to be cleaned.
|
|
81
|
-
extension :
|
|
82
|
-
FITS extension to use (default is 0).
|
|
85
|
+
extension : str, optional
|
|
86
|
+
FITS extension to use (default is "0").
|
|
83
87
|
auxfile : str, optional
|
|
84
88
|
Path to an auxiliary FITS file (default is None).
|
|
85
|
-
extension_auxfile :
|
|
86
|
-
FITS extension for auxiliary file (default is 0).
|
|
89
|
+
extension_auxfile : str, optional
|
|
90
|
+
FITS extension for auxiliary file (default is "0").
|
|
87
91
|
fontfamily : str, optional
|
|
88
92
|
Font family for the GUI (default is "Helvetica").
|
|
89
93
|
fontsize : int, optional
|
|
90
94
|
Font size for the GUI (default is 14).
|
|
95
|
+
width : int, optional
|
|
96
|
+
Width of the GUI window in pixels (default is 800).
|
|
97
|
+
height : int, optional
|
|
98
|
+
Height of the GUI window in pixels (default is 600).
|
|
99
|
+
verbose : bool, optional
|
|
100
|
+
Enable verbose output (default is False).
|
|
91
101
|
|
|
92
102
|
Methods
|
|
93
103
|
-------
|
|
104
|
+
process_detected_cr(dilation)
|
|
105
|
+
Process the detected cosmic ray mask.
|
|
106
|
+
load_detected_cr_from_file()
|
|
107
|
+
Load detected cosmic ray mask from a FITS file.
|
|
94
108
|
load_fits_file()
|
|
95
109
|
Load the FITS file and auxiliary file (if provided).
|
|
96
110
|
save_fits_file()
|
|
@@ -124,6 +138,12 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
124
138
|
Font size for the GUI.
|
|
125
139
|
default_font : tkfont.Font
|
|
126
140
|
The default font used in the GUI.
|
|
141
|
+
width : int
|
|
142
|
+
Width of the GUI window in pixels.
|
|
143
|
+
height : int
|
|
144
|
+
Height of the GUI window in pixels.
|
|
145
|
+
verbose : bool
|
|
146
|
+
Enable verbose output.
|
|
127
147
|
lacosmic_params : dict
|
|
128
148
|
Dictionary of L.A.Cosmic parameters.
|
|
129
149
|
input_fits : str
|
|
@@ -171,6 +191,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
171
191
|
# self.root.geometry("800x800+50+0") # This does not work in Fedora
|
|
172
192
|
self.width = width
|
|
173
193
|
self.height = height
|
|
194
|
+
self.verbose = verbose
|
|
174
195
|
self.root.minsize(self.width, self.height)
|
|
175
196
|
self.root.update_idletasks()
|
|
176
197
|
self.root.title("Cosmic Ray Cleaner")
|
|
@@ -181,6 +202,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
181
202
|
family=fontfamily, size=fontsize, weight="normal", slant="roman", underline=0, overstrike=0
|
|
182
203
|
)
|
|
183
204
|
self.lacosmic_params = lacosmic_default_dict.copy()
|
|
205
|
+
self.lacosmic_params["run1_verbose"]["value"] = self.verbose
|
|
206
|
+
self.lacosmic_params["run2_verbose"]["value"] = self.verbose
|
|
184
207
|
self.input_fits = input_fits
|
|
185
208
|
self.extension = extension
|
|
186
209
|
self.data = None
|
|
@@ -202,6 +225,93 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
202
225
|
self.num_features = 0
|
|
203
226
|
self.working_in_review_window = False
|
|
204
227
|
|
|
228
|
+
def process_detected_cr(self, dilation):
|
|
229
|
+
"""Process the detected cosmic ray mask.
|
|
230
|
+
|
|
231
|
+
Parameters
|
|
232
|
+
----------
|
|
233
|
+
dilation : int
|
|
234
|
+
Number of pixels to dilate the cosmic ray mask.
|
|
235
|
+
"""
|
|
236
|
+
# Process the mask: dilation and labeling
|
|
237
|
+
if np.any(self.mask_crfound):
|
|
238
|
+
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
239
|
+
if dilation > 0:
|
|
240
|
+
# Dilate the mask by the specified number of pixels
|
|
241
|
+
self.mask_crfound = dilatemask(
|
|
242
|
+
mask=self.mask_crfound, iterations=dilation, connectivity=1
|
|
243
|
+
)
|
|
244
|
+
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
245
|
+
sdum = str(num_cr_pixels_after_dilation)
|
|
246
|
+
else:
|
|
247
|
+
sdum = str(num_cr_pixels_before_dilation)
|
|
248
|
+
print(
|
|
249
|
+
"Number of cosmic ray pixels detected..........: "
|
|
250
|
+
f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
|
|
251
|
+
)
|
|
252
|
+
if dilation > 0:
|
|
253
|
+
print(
|
|
254
|
+
f"Number of cosmic ray pixels after dilation....: "
|
|
255
|
+
f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
|
|
256
|
+
)
|
|
257
|
+
# Label connected components in the mask; note that by default,
|
|
258
|
+
# structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
|
|
259
|
+
# diagonal connections too, so we define a 3x3 square.
|
|
260
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
261
|
+
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
262
|
+
print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
263
|
+
self.replace_detected_cr_button.config(state=tk.NORMAL)
|
|
264
|
+
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
265
|
+
self.update_cr_overlay()
|
|
266
|
+
self.use_cursor = True
|
|
267
|
+
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
268
|
+
else:
|
|
269
|
+
print("No cosmic ray pixels detected!")
|
|
270
|
+
self.cr_labels = None
|
|
271
|
+
self.num_features = 0
|
|
272
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
273
|
+
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
274
|
+
|
|
275
|
+
def load_detected_cr_from_file(self):
|
|
276
|
+
"""Load detected cosmic ray mask from a FITS file."""
|
|
277
|
+
crmask_file = filedialog.askopenfilename(
|
|
278
|
+
initialdir=os.getcwd(),
|
|
279
|
+
title="Select FITS file with cosmic ray mask",
|
|
280
|
+
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
281
|
+
)
|
|
282
|
+
if crmask_file:
|
|
283
|
+
print(f"Selected input FITS file: {crmask_file}")
|
|
284
|
+
extension = simpledialog.askstring(
|
|
285
|
+
"Select Extension",
|
|
286
|
+
f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
|
|
287
|
+
initialvalue=None,
|
|
288
|
+
)
|
|
289
|
+
try:
|
|
290
|
+
extension = int(extension)
|
|
291
|
+
except ValueError:
|
|
292
|
+
pass # Keep as string
|
|
293
|
+
dilation = simpledialog.askinteger(
|
|
294
|
+
"Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
|
|
295
|
+
)
|
|
296
|
+
try:
|
|
297
|
+
with fits.open(crmask_file, mode="readonly") as hdul:
|
|
298
|
+
if isinstance(extension, int):
|
|
299
|
+
if extension < 0 or extension >= len(hdul):
|
|
300
|
+
raise IndexError(f"Extension index {extension} out of range.")
|
|
301
|
+
else:
|
|
302
|
+
if extension not in hdul:
|
|
303
|
+
raise KeyError(f"Extension name '{extension}' not found.")
|
|
304
|
+
mask_crfound_loaded = hdul[extension].data.astype(bool)
|
|
305
|
+
if mask_crfound_loaded.shape != self.data.shape:
|
|
306
|
+
print(f"data shape...: {self.data.shape}")
|
|
307
|
+
print(f"mask shape...: {mask_crfound_loaded.shape}")
|
|
308
|
+
raise ValueError("Cosmic ray mask has different shape.")
|
|
309
|
+
self.mask_crfound = mask_crfound_loaded
|
|
310
|
+
print(f"Loaded cosmic ray mask from {crmask_file}")
|
|
311
|
+
self.process_detected_cr(dilation=dilation)
|
|
312
|
+
except Exception as e:
|
|
313
|
+
print(f"Error loading cosmic ray mask: {e}")
|
|
314
|
+
|
|
205
315
|
def load_fits_file(self):
|
|
206
316
|
"""Load the FITS file and auxiliary file (if provided).
|
|
207
317
|
|
|
@@ -216,8 +326,22 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
216
326
|
provided, it also loads the auxiliary data from the specified extension.
|
|
217
327
|
The loaded data is stored in `self.data` and `self.auxdata` attributes.
|
|
218
328
|
"""
|
|
329
|
+
# check if extension is compatible with an integer
|
|
330
|
+
try:
|
|
331
|
+
extnum = int(self.extension)
|
|
332
|
+
self.extension = extnum
|
|
333
|
+
except ValueError:
|
|
334
|
+
# Keep as string (delaying checking until opening the file)
|
|
335
|
+
self.extension = self.extension.upper() # Convert to uppercase
|
|
219
336
|
try:
|
|
220
337
|
with fits.open(self.input_fits, mode="readonly") as hdul:
|
|
338
|
+
if isinstance(self.extension, int):
|
|
339
|
+
if self.extension < 0 or self.extension >= len(hdul):
|
|
340
|
+
raise IndexError(f"Extension index {self.extension} out of range.")
|
|
341
|
+
else:
|
|
342
|
+
if self.extension not in hdul:
|
|
343
|
+
raise KeyError(f"Extension name '{self.extension}' not found.")
|
|
344
|
+
print(f"Reading file [bold green]{self.input_fits}[/bold green], extension {self.extension}")
|
|
221
345
|
self.data = hdul[self.extension].data
|
|
222
346
|
if "CRMASK" in hdul:
|
|
223
347
|
self.mask_fixed = hdul["CRMASK"].data.astype(bool)
|
|
@@ -225,13 +349,28 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
225
349
|
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
226
350
|
except Exception as e:
|
|
227
351
|
print(f"Error loading FITS file: {e}")
|
|
352
|
+
sys.exit(1)
|
|
228
353
|
self.mask_crfound = np.zeros(self.data.shape, dtype=bool)
|
|
229
354
|
naxis2, naxis1 = self.data.shape
|
|
230
355
|
self.region = SliceRegion2D(f"[1:{naxis1}, 1:{naxis2}]", mode="fits").python
|
|
231
356
|
# Read auxiliary file if provided
|
|
232
357
|
if self.auxfile is not None:
|
|
358
|
+
# check if extension_auxfile is compatible with an integer
|
|
359
|
+
try:
|
|
360
|
+
extnum_aux = int(self.extension_auxfile)
|
|
361
|
+
self.extension_auxfile = extnum_aux
|
|
362
|
+
except ValueError:
|
|
363
|
+
# Keep as string (delaying checking until opening the file)
|
|
364
|
+
self.extension_auxfile = self.extension_auxfile.upper() # Convert to uppercase
|
|
233
365
|
try:
|
|
234
366
|
with fits.open(self.auxfile, mode="readonly") as hdul_aux:
|
|
367
|
+
if isinstance(self.extension_auxfile, int):
|
|
368
|
+
if self.extension_auxfile < 0 or self.extension_auxfile >= len(hdul_aux):
|
|
369
|
+
raise IndexError(f"Extension index {self.extension_auxfile} out of range.")
|
|
370
|
+
else:
|
|
371
|
+
if self.extension_auxfile not in hdul_aux:
|
|
372
|
+
raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
|
|
373
|
+
print(f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}")
|
|
235
374
|
self.auxdata = hdul_aux[self.extension_auxfile].data
|
|
236
375
|
if self.auxdata.shape != self.data.shape:
|
|
237
376
|
print(f"data shape...: {self.data.shape}")
|
|
@@ -308,20 +447,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
308
447
|
self.button_frame1.pack(pady=5)
|
|
309
448
|
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
310
449
|
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(
|
|
450
|
+
self.load_detected_cr_button = tk.Button(
|
|
451
|
+
self.button_frame1, text="Load detected CRs", command=self.load_detected_cr_from_file
|
|
452
|
+
)
|
|
453
|
+
self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
454
|
+
self.replace_detected_cr_button = tk.Button(
|
|
321
455
|
self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
|
|
322
456
|
)
|
|
323
|
-
self.
|
|
324
|
-
self.
|
|
457
|
+
self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
|
|
458
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
|
|
325
459
|
self.examine_detected_cr_button = tk.Button(
|
|
326
460
|
self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
|
|
327
461
|
)
|
|
@@ -331,6 +465,17 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
331
465
|
# Row 2 of buttons
|
|
332
466
|
self.button_frame2 = tk.Frame(self.root)
|
|
333
467
|
self.button_frame2.pack(pady=5)
|
|
468
|
+
self.toggle_auxdata_button = tk.Button(self.button_frame2, text="[t]oggle data", command=self.toggle_auxdata)
|
|
469
|
+
self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
|
|
470
|
+
if self.auxdata is None:
|
|
471
|
+
self.toggle_auxdata_button.config(state=tk.DISABLED)
|
|
472
|
+
else:
|
|
473
|
+
self.toggle_auxdata_button.config(state=tk.NORMAL)
|
|
474
|
+
self.image_aspect = "equal"
|
|
475
|
+
self.toggle_aspect_button = tk.Button(
|
|
476
|
+
self.button_frame2, text=f"[a]spect: {self.image_aspect}", command=self.toggle_aspect
|
|
477
|
+
)
|
|
478
|
+
self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
|
|
334
479
|
self.save_button = tk.Button(self.button_frame2, text="Save cleaned FITS", command=self.save_fits_file)
|
|
335
480
|
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
336
481
|
self.save_button.config(state=tk.DISABLED) # Initially disabled
|
|
@@ -340,6 +485,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
340
485
|
# Row 3 of buttons
|
|
341
486
|
self.button_frame3 = tk.Frame(self.root)
|
|
342
487
|
self.button_frame3.pack(pady=5)
|
|
488
|
+
self.use_cursor = False
|
|
489
|
+
self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
|
|
490
|
+
self.use_cursor_button.pack(side=tk.LEFT, padx=5)
|
|
343
491
|
vmin, vmax = zscale(self.data)
|
|
344
492
|
self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
|
|
345
493
|
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
@@ -349,6 +497,19 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
349
497
|
self.set_minmax_button.pack(side=tk.LEFT, padx=5)
|
|
350
498
|
self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
|
|
351
499
|
self.set_zscale_button.pack(side=tk.LEFT, padx=5)
|
|
500
|
+
if self.overplot_cr_pixels:
|
|
501
|
+
self.overplot_cr_button = tk.Button(
|
|
502
|
+
self.button_frame3,
|
|
503
|
+
text="CR overlay: ON ",
|
|
504
|
+
command=self.toggle_cr_overlay,
|
|
505
|
+
)
|
|
506
|
+
else:
|
|
507
|
+
self.overplot_cr_button = tk.Button(
|
|
508
|
+
self.button_frame3,
|
|
509
|
+
text="CR overlay: OFF",
|
|
510
|
+
command=self.toggle_cr_overlay,
|
|
511
|
+
)
|
|
512
|
+
self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
|
|
352
513
|
|
|
353
514
|
# Figure
|
|
354
515
|
self.plot_frame = tk.Frame(self.root)
|
|
@@ -378,19 +539,63 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
378
539
|
xlabel = "X pixel (from 1 to NAXIS1)"
|
|
379
540
|
ylabel = "Y pixel (from 1 to NAXIS2)"
|
|
380
541
|
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
542
|
+
self.image_aspect = "equal"
|
|
543
|
+
self.displaying_auxdata = False
|
|
381
544
|
self.image, _, _ = imshow(
|
|
382
|
-
self.fig,
|
|
383
|
-
self.ax,
|
|
384
|
-
self.data,
|
|
545
|
+
fig=self.fig,
|
|
546
|
+
ax=self.ax,
|
|
547
|
+
data=self.data,
|
|
385
548
|
vmin=vmin,
|
|
386
549
|
vmax=vmax,
|
|
387
|
-
title=os.path.basename(self.input_fits),
|
|
550
|
+
title=f"data: {os.path.basename(self.input_fits)}",
|
|
388
551
|
xlabel=xlabel,
|
|
389
552
|
ylabel=ylabel,
|
|
390
553
|
extent=extent,
|
|
554
|
+
aspect=self.image_aspect,
|
|
391
555
|
)
|
|
392
556
|
self.fig.tight_layout()
|
|
393
557
|
|
|
558
|
+
def set_cursor_onoff(self):
|
|
559
|
+
"""Toggle cursor selection mode on or off."""
|
|
560
|
+
if not self.use_cursor:
|
|
561
|
+
self.use_cursor = True
|
|
562
|
+
self.use_cursor_button.config(text="[c]ursor: ON ")
|
|
563
|
+
else:
|
|
564
|
+
self.use_cursor = False
|
|
565
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
566
|
+
|
|
567
|
+
def toggle_auxdata(self):
|
|
568
|
+
"""Toggle between main data and auxiliary data for display."""
|
|
569
|
+
if self.displaying_auxdata:
|
|
570
|
+
# Switch to main data
|
|
571
|
+
vmin = self.get_vmin()
|
|
572
|
+
vmax = self.get_vmax()
|
|
573
|
+
self.image.set_data(self.data)
|
|
574
|
+
self.image.set_clim(vmin=vmin, vmax=vmax)
|
|
575
|
+
self.displaying_auxdata = False
|
|
576
|
+
self.ax.set_title(f"data: {os.path.basename(self.input_fits)}")
|
|
577
|
+
else:
|
|
578
|
+
# Switch to auxiliary data
|
|
579
|
+
vmin = self.get_vmin()
|
|
580
|
+
vmax = self.get_vmax()
|
|
581
|
+
self.image.set_data(self.auxdata)
|
|
582
|
+
self.image.set_clim(vmin=vmin, vmax=vmax)
|
|
583
|
+
self.displaying_auxdata = True
|
|
584
|
+
self.ax.set_title(f"auxdata: {os.path.basename(self.auxfile)}")
|
|
585
|
+
self.canvas.draw_idle()
|
|
586
|
+
|
|
587
|
+
def toggle_aspect(self):
|
|
588
|
+
"""Toggle the aspect ratio of the image display."""
|
|
589
|
+
if self.image_aspect == "equal":
|
|
590
|
+
self.image_aspect = "auto"
|
|
591
|
+
else:
|
|
592
|
+
self.image_aspect = "equal"
|
|
593
|
+
print(f"Setting image aspect to: {self.image_aspect}")
|
|
594
|
+
self.toggle_aspect_button.config(text=f"[a]spect: {self.image_aspect}")
|
|
595
|
+
self.ax.set_aspect(self.image_aspect)
|
|
596
|
+
self.fig.tight_layout()
|
|
597
|
+
self.canvas.draw_idle()
|
|
598
|
+
|
|
394
599
|
def run_lacosmic(self):
|
|
395
600
|
"""Run L.A.Cosmic to detect cosmic rays."""
|
|
396
601
|
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
@@ -427,17 +632,18 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
427
632
|
usefulmask[usefulregion] = 1.0
|
|
428
633
|
# Update parameter dictionary with new values
|
|
429
634
|
self.lacosmic_params = updated_params
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
635
|
+
if self.verbose:
|
|
636
|
+
print("Parameters updated:")
|
|
637
|
+
for key, info in self.lacosmic_params.items():
|
|
638
|
+
print(f" {key}: {info['value']}")
|
|
433
639
|
if self.lacosmic_params["nruns"]["value"] not in [1, 2]:
|
|
434
640
|
raise ValueError("nruns must be 1 or 2")
|
|
435
641
|
# Execute L.A.Cosmic with updated parameters
|
|
436
642
|
print("[bold green]Executing L.A.Cosmic (run 1)...[/bold green]")
|
|
437
643
|
borderpadd = updated_params["borderpadd"]["value"]
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
ccd=
|
|
644
|
+
cleandata_lacosmic, mask_crfound = lacosmicpad(
|
|
645
|
+
pad_width=borderpadd,
|
|
646
|
+
ccd=self.data,
|
|
441
647
|
gain=self.lacosmic_params["run1_gain"]["value"],
|
|
442
648
|
readnoise=self.lacosmic_params["run1_readnoise"]["value"],
|
|
443
649
|
sigclip=self.lacosmic_params["run1_sigclip"]["value"],
|
|
@@ -446,16 +652,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
446
652
|
niter=self.lacosmic_params["run1_niter"]["value"],
|
|
447
653
|
verbose=self.lacosmic_params["run1_verbose"]["value"],
|
|
448
654
|
)
|
|
449
|
-
cleandata_lacosmic = cleandata_lacosmic[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
450
|
-
mask_crfound = mask_crfound[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
451
655
|
# Apply usefulmask to consider only selected region
|
|
452
656
|
cleandata_lacosmic *= usefulmask
|
|
453
657
|
mask_crfound = mask_crfound & (usefulmask.astype(bool))
|
|
454
658
|
# Second execution if nruns == 2
|
|
455
659
|
if self.lacosmic_params["nruns"]["value"] == 2:
|
|
456
660
|
print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
|
|
457
|
-
cleandata_lacosmic2, mask_crfound2 =
|
|
458
|
-
|
|
661
|
+
cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
|
|
662
|
+
pad_width=borderpadd,
|
|
663
|
+
ccd=self.data,
|
|
459
664
|
gain=self.lacosmic_params["run2_gain"]["value"],
|
|
460
665
|
readnoise=self.lacosmic_params["run2_readnoise"]["value"],
|
|
461
666
|
sigclip=self.lacosmic_params["run2_sigclip"]["value"],
|
|
@@ -464,8 +669,6 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
464
669
|
niter=self.lacosmic_params["run2_niter"]["value"],
|
|
465
670
|
verbose=self.lacosmic_params["run2_verbose"]["value"],
|
|
466
671
|
)
|
|
467
|
-
cleandata_lacosmic2 = cleandata_lacosmic2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
468
|
-
mask_crfound2 = mask_crfound2[borderpadd:-borderpadd, borderpadd:-borderpadd]
|
|
469
672
|
# Apply usefulmask to consider only selected region
|
|
470
673
|
cleandata_lacosmic2 *= usefulmask
|
|
471
674
|
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
@@ -473,19 +676,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
473
676
|
if np.any(mask_crfound):
|
|
474
677
|
print(f"Number of cosmic ray pixels (run1).......: {np.sum(mask_crfound)}")
|
|
475
678
|
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
|
|
679
|
+
mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2)
|
|
489
680
|
print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
|
|
490
681
|
# Use the cleandata from the second run
|
|
491
682
|
cleandata_lacosmic = cleandata_lacosmic2
|
|
@@ -495,42 +686,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
495
686
|
self.mask_crfound = np.zeros_like(self.data, dtype=bool)
|
|
496
687
|
self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
|
|
497
688
|
# 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)
|
|
689
|
+
self.process_detected_cr(dilation=self.lacosmic_params["dilation"]["value"])
|
|
534
690
|
else:
|
|
535
691
|
print("Parameter editing cancelled. L.A.Cosmic detection skipped!")
|
|
536
692
|
self.run_lacosmic_button.config(state=tk.NORMAL)
|
|
@@ -539,9 +695,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
539
695
|
"""Toggle the overlay of cosmic ray pixels on the image."""
|
|
540
696
|
self.overplot_cr_pixels = not self.overplot_cr_pixels
|
|
541
697
|
if self.overplot_cr_pixels:
|
|
542
|
-
self.overplot_cr_button.config(text="CR overlay:
|
|
698
|
+
self.overplot_cr_button.config(text="CR overlay: ON ")
|
|
543
699
|
else:
|
|
544
|
-
self.overplot_cr_button.config(text="CR overlay:
|
|
700
|
+
self.overplot_cr_button.config(text="CR overlay: OFF")
|
|
545
701
|
self.update_cr_overlay()
|
|
546
702
|
|
|
547
703
|
def update_cr_overlay(self):
|
|
@@ -699,9 +855,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
699
855
|
# recalculate labels and number of features
|
|
700
856
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
701
857
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
702
|
-
|
|
858
|
+
num_cr_remaining = np.sum(self.mask_crfound)
|
|
859
|
+
sdum = str(num_cr_remaining)
|
|
703
860
|
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
704
861
|
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
862
|
+
if num_cr_remaining == 0:
|
|
863
|
+
self.use_cursor = False
|
|
864
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
705
865
|
# redraw image to show the changes
|
|
706
866
|
self.image.set_data(self.data)
|
|
707
867
|
self.canvas.draw_idle()
|
|
@@ -709,7 +869,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
709
869
|
self.save_button.config(state=tk.NORMAL)
|
|
710
870
|
if self.num_features == 0:
|
|
711
871
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
712
|
-
self.
|
|
872
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
713
873
|
self.update_cr_overlay()
|
|
714
874
|
|
|
715
875
|
def examine_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
|
|
@@ -765,9 +925,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
765
925
|
# recalculate labels and number of features
|
|
766
926
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
767
927
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
768
|
-
|
|
928
|
+
num_remaining = np.sum(self.mask_crfound)
|
|
929
|
+
sdum = str(num_remaining)
|
|
769
930
|
print(f"Remaining number of cosmic ray pixels...................: {sdum}")
|
|
770
931
|
print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
932
|
+
if num_remaining == 0:
|
|
933
|
+
self.use_cursor = False
|
|
934
|
+
self.use_cursor_button.config(text="[c]ursor: OFF")
|
|
771
935
|
# redraw image to show the changes
|
|
772
936
|
self.image.set_data(self.data)
|
|
773
937
|
self.canvas.draw_idle()
|
|
@@ -775,7 +939,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
775
939
|
self.save_button.config(state=tk.NORMAL)
|
|
776
940
|
if self.num_features == 0:
|
|
777
941
|
self.examine_detected_cr_button.config(state=tk.DISABLED)
|
|
778
|
-
self.
|
|
942
|
+
self.replace_detected_cr_button.config(state=tk.DISABLED)
|
|
779
943
|
self.update_cr_overlay()
|
|
780
944
|
|
|
781
945
|
def stop_app(self):
|
|
@@ -792,14 +956,39 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
792
956
|
|
|
793
957
|
def on_key(self, event):
|
|
794
958
|
"""Handle key press events."""
|
|
795
|
-
if event.key == "
|
|
796
|
-
|
|
959
|
+
if event.key == "c":
|
|
960
|
+
self.set_cursor_onoff()
|
|
961
|
+
elif event.key == "a":
|
|
962
|
+
self.toggle_aspect()
|
|
963
|
+
elif event.key == "t" and self.auxdata is not None:
|
|
964
|
+
self.toggle_auxdata()
|
|
797
965
|
elif event.key == ",":
|
|
798
966
|
self.set_minmax()
|
|
799
967
|
elif event.key == "/":
|
|
800
968
|
self.set_zscale()
|
|
801
|
-
|
|
802
|
-
|
|
969
|
+
elif event.key == "o":
|
|
970
|
+
self.toolbar.zoom()
|
|
971
|
+
elif event.key == "h":
|
|
972
|
+
self.toolbar.home()
|
|
973
|
+
elif event.key == "p":
|
|
974
|
+
self.toolbar.pan()
|
|
975
|
+
elif event.key == "s":
|
|
976
|
+
self.toolbar.save_figure()
|
|
977
|
+
elif event.key == "?":
|
|
978
|
+
# Display list of keyboard shortcuts
|
|
979
|
+
print("[bold blue]Keyboard Shortcuts:[/bold blue]")
|
|
980
|
+
print("[red] c [/red]: Toggle cursor selection mode on/off")
|
|
981
|
+
print("[red] t [/red]: Toggle between main data and auxiliary data")
|
|
982
|
+
print("[red] a [/red]: Toggle image aspect ratio equal/auto")
|
|
983
|
+
print("[red] , [/red]: Set vmin and vmax to minmax")
|
|
984
|
+
print("[red] / [/red]: Set vmin and vmax using zscale")
|
|
985
|
+
print("[red] h [/red]: Go to home view \\[toolbar]")
|
|
986
|
+
print("[red] o [/red]: Activate zoom mode \\[toolbar]")
|
|
987
|
+
print("[red] p [/red]: Activate pan mode \\[toolbar]")
|
|
988
|
+
print("[red] s [/red]: Save the current figure \\[toolbar]")
|
|
989
|
+
print("[red] q [/red]: (ignored) prevent closing the window")
|
|
990
|
+
elif event.key == "q":
|
|
991
|
+
pass # Ignore the "q" key to prevent closing the window
|
|
803
992
|
|
|
804
993
|
def on_click(self, event):
|
|
805
994
|
"""Handle mouse click events on the image."""
|
|
@@ -814,6 +1003,10 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
814
1003
|
print(f"Toolbar mode '{toolbar.mode}' active; click ignored.")
|
|
815
1004
|
return
|
|
816
1005
|
|
|
1006
|
+
# proceed only if cursor selection mode is on
|
|
1007
|
+
if not self.use_cursor:
|
|
1008
|
+
return
|
|
1009
|
+
|
|
817
1010
|
# ignore clicks outside the expected axes
|
|
818
1011
|
# (note that the color bar is a different axes)
|
|
819
1012
|
if event.inaxes == self.ax:
|
|
@@ -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
|
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
|
@@ -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=oqn_gabhd81ghdha2yfonHK150M_SY20r86f1CEW9bg,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/cosmicraycleanerapp.py,sha256=C-lGOREVXrlaqJsjEuJ66xVNx0z8axXq5f5GQh9dpMA,41226
|
|
25
|
+
teareduce/cleanest/cosmicraycleanerapp.py,sha256=FKeQYCUhvod6HDSOqZN79QhVoujhC2_iQsyP1tF3P9o,49726
|
|
27
26
|
teareduce/cleanest/definitions.py,sha256=HLv41cuhUOUyEicRHea0LRTv4Wagm24miSxFt4cZN3g,2546
|
|
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
34
|
teareduce/cleanest/interpolationeditor.py,sha256=EksuNwKOM79MyGlAgxb9j-v7pIFXV8QOcyZDLVQlQjU,13221
|
|
35
|
-
teareduce/cleanest/
|
|
36
|
-
teareduce/cleanest/
|
|
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
|
|
37
39
|
teareduce/cleanest/reviewcosmicray.py,sha256=_qO9ifkAMqB4Wuvq2wIYZ8_DUniB2lWcxn9l404hXnY,28210
|
|
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.7.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
47
|
+
teareduce-0.5.7.dist-info/METADATA,sha256=q725fGixcsrRvW2iS9-QRgBPi54dZqjOILxmzEDBShs,3113
|
|
48
|
+
teareduce-0.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
49
|
+
teareduce-0.5.7.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
|
|
50
|
+
teareduce-0.5.7.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
|
|
51
|
+
teareduce-0.5.7.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|