teareduce 0.4.7__py3-none-any.whl → 0.4.9__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/__init__.py +1 -0
- teareduce/cleanest/__init__.py +0 -0
- teareduce/cleanest/__main__.py +49 -0
- teareduce/cleanest/cosmicraycleanerapp.py +203 -0
- teareduce/{cleanest.py → cleanest/reviewcosmicray.py} +4 -212
- teareduce/cookbook/__init__.py +0 -0
- teareduce/cookbook/get_cookbook_file.py +34 -0
- teareduce/imshow.py +17 -4
- teareduce/sliceregion.py +12 -12
- teareduce/version.py +1 -1
- {teareduce-0.4.7.dist-info → teareduce-0.4.9.dist-info}/METADATA +1 -1
- {teareduce-0.4.7.dist-info → teareduce-0.4.9.dist-info}/RECORD +16 -11
- teareduce-0.4.9.dist-info/entry_points.txt +2 -0
- teareduce-0.4.7.dist-info/entry_points.txt +0 -2
- {teareduce-0.4.7.dist-info → teareduce-0.4.9.dist-info}/WHEEL +0 -0
- {teareduce-0.4.7.dist-info → teareduce-0.4.9.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.4.7.dist-info → teareduce-0.4.9.dist-info}/top_level.txt +0 -0
teareduce/__init__.py
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#
|
|
9
9
|
|
|
10
10
|
from .avoid_astropy_warnings import avoid_astropy_warnings
|
|
11
|
+
from .cookbook.get_cookbook_file import get_cookbook_file
|
|
11
12
|
from .correct_pincushion_distortion import correct_pincushion_distortion
|
|
12
13
|
from .cosmicrays import cr2images, apply_cr2images_ccddata, crmedian
|
|
13
14
|
from .ctext import ctext
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
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
|
+
"""Interactive Cosmic Ray cleaning tool."""
|
|
11
|
+
|
|
12
|
+
import argparse
|
|
13
|
+
import tkinter as tk
|
|
14
|
+
import os
|
|
15
|
+
|
|
16
|
+
from .cosmicraycleanerapp import CosmicRayCleanerApp
|
|
17
|
+
|
|
18
|
+
import matplotlib
|
|
19
|
+
matplotlib.use("TkAgg")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def main():
|
|
23
|
+
parser = argparse.ArgumentParser(description="Interactive cosmic ray cleaner for FITS images.")
|
|
24
|
+
parser.add_argument("input_fits", help="Path to the FITS file to be cleaned.")
|
|
25
|
+
parser.add_argument("--extension", type=int, default=0,
|
|
26
|
+
help="FITS extension to use (default: 0).")
|
|
27
|
+
parser.add_argument("--output_fits", type=str, default=None,
|
|
28
|
+
help="Path to save the cleaned FITS file")
|
|
29
|
+
args = parser.parse_args()
|
|
30
|
+
|
|
31
|
+
if not os.path.isfile(args.input_fits):
|
|
32
|
+
print(f"Error: File '{args.input_fits}' does not exist.")
|
|
33
|
+
return
|
|
34
|
+
if args.output_fits is not None and os.path.isfile(args.output_fits):
|
|
35
|
+
print(f"Error: Output file '{args.output_fits}' already exists.")
|
|
36
|
+
return
|
|
37
|
+
|
|
38
|
+
# Initialize Tkinter root
|
|
39
|
+
root = tk.Tk()
|
|
40
|
+
|
|
41
|
+
# Create and run the application
|
|
42
|
+
CosmicRayCleanerApp(root, args.input_fits, args.extension, args.output_fits)
|
|
43
|
+
|
|
44
|
+
# Execute
|
|
45
|
+
root.mainloop()
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
if __name__ == "__main__":
|
|
49
|
+
main()
|
|
@@ -0,0 +1,203 @@
|
|
|
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
|
+
"""Define the CosmicRayCleanerApp class."""
|
|
11
|
+
|
|
12
|
+
import tkinter as tk
|
|
13
|
+
from tkinter import filedialog
|
|
14
|
+
from tkinter import simpledialog
|
|
15
|
+
|
|
16
|
+
from astropy.io import fits
|
|
17
|
+
from ccdproc import cosmicray_lacosmic
|
|
18
|
+
import matplotlib.pyplot as plt
|
|
19
|
+
from matplotlib.backend_bases import key_press_handler
|
|
20
|
+
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
21
|
+
import numpy as np
|
|
22
|
+
import os
|
|
23
|
+
|
|
24
|
+
from .reviewcosmicray import ReviewCosmicRay
|
|
25
|
+
|
|
26
|
+
from ..imshow import imshow
|
|
27
|
+
from ..zscale import zscale
|
|
28
|
+
|
|
29
|
+
import matplotlib
|
|
30
|
+
matplotlib.use("TkAgg")
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
class CosmicRayCleanerApp():
|
|
34
|
+
"""Main application class for cosmic ray cleaning."""
|
|
35
|
+
|
|
36
|
+
def __init__(self, root, input_fits, extension=0, output_fits=None):
|
|
37
|
+
"""
|
|
38
|
+
Initialize the application.
|
|
39
|
+
|
|
40
|
+
Parameters
|
|
41
|
+
----------
|
|
42
|
+
root : tk.Tk
|
|
43
|
+
The main Tkinter window.
|
|
44
|
+
input_fits : str
|
|
45
|
+
Path to the FITS file to be cleaned.
|
|
46
|
+
extension : int, optional
|
|
47
|
+
FITS extension to use (default is 0).
|
|
48
|
+
output_fits : str, optional
|
|
49
|
+
Path to save the cleaned FITS file (default is None, which prompts
|
|
50
|
+
for a save location).
|
|
51
|
+
"""
|
|
52
|
+
self.root = root
|
|
53
|
+
self.root.title("Cosmic Ray Cleaner")
|
|
54
|
+
self.root.geometry("800x700+50+0")
|
|
55
|
+
self.input_fits = input_fits
|
|
56
|
+
self.extension = extension
|
|
57
|
+
self.output_fits = output_fits
|
|
58
|
+
self.load_fits_file()
|
|
59
|
+
self.create_widgets()
|
|
60
|
+
|
|
61
|
+
def load_fits_file(self):
|
|
62
|
+
try:
|
|
63
|
+
with fits.open(self.input_fits, mode='readonly') as hdul:
|
|
64
|
+
self.data = hdul[self.extension].data
|
|
65
|
+
if 'CRMASK' in hdul:
|
|
66
|
+
self.mask_fixed = hdul['CRMASK'].data.astype(bool)
|
|
67
|
+
else:
|
|
68
|
+
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
69
|
+
except Exception as e:
|
|
70
|
+
print(f"Error loading FITS file: {e}")
|
|
71
|
+
|
|
72
|
+
def save_fits_file(self):
|
|
73
|
+
if self.output_fits is None:
|
|
74
|
+
base, ext = os.path.splitext(self.input_fits)
|
|
75
|
+
suggested_name = f"{base}_cleaned"
|
|
76
|
+
else:
|
|
77
|
+
suggested_name, _ = os.path.splitext(self.output_fits)
|
|
78
|
+
self.output_fits = filedialog.asksaveasfilename(
|
|
79
|
+
initialdir=os.getcwd(),
|
|
80
|
+
title="Save cleaned FITS file",
|
|
81
|
+
defaultextension=".fits",
|
|
82
|
+
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
83
|
+
initialfile=suggested_name
|
|
84
|
+
)
|
|
85
|
+
try:
|
|
86
|
+
with fits.open(self.input_fits, mode='readonly') as hdul:
|
|
87
|
+
hdul[self.extension].data = self.data
|
|
88
|
+
if 'CRMASK' in hdul:
|
|
89
|
+
hdul['CRMASK'].data = self.mask_fixed.astype(np.uint8)
|
|
90
|
+
else:
|
|
91
|
+
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name='CRMASK')
|
|
92
|
+
hdul.append(crmask_hdu)
|
|
93
|
+
hdul.writeto(self.output_fits, overwrite=True)
|
|
94
|
+
print(f"Cleaned data saved to {self.output_fits}")
|
|
95
|
+
except Exception as e:
|
|
96
|
+
print(f"Error saving FITS file: {e}")
|
|
97
|
+
|
|
98
|
+
def create_widgets(self):
|
|
99
|
+
# Row 1
|
|
100
|
+
self.button_frame1 = tk.Frame(self.root)
|
|
101
|
+
self.button_frame1.grid(row=0, column=0, pady=5)
|
|
102
|
+
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
103
|
+
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
104
|
+
self.save_button = tk.Button(self.button_frame1, text="Save cleaned FITS", command=self.save_fits_file)
|
|
105
|
+
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
106
|
+
|
|
107
|
+
# Row 2
|
|
108
|
+
self.button_frame2 = tk.Frame(self.root)
|
|
109
|
+
self.button_frame2.grid(row=1, column=0, pady=5)
|
|
110
|
+
vmin, vmax = zscale(self.data)
|
|
111
|
+
self.vmin_button = tk.Button(self.button_frame2, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
|
|
112
|
+
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
113
|
+
self.vmax_button = tk.Button(self.button_frame2, text=f"vmax: {vmax:.2f}", command=self.set_vmax)
|
|
114
|
+
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
115
|
+
self.stop_button = tk.Button(self.button_frame2, text="Stop program", command=self.stop_app)
|
|
116
|
+
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
117
|
+
|
|
118
|
+
# Main frame for figure and toolbar
|
|
119
|
+
self.main_frame = tk.Frame(self.root)
|
|
120
|
+
self.main_frame.grid(row=2, column=0, sticky="nsew")
|
|
121
|
+
self.root.grid_rowconfigure(2, weight=1)
|
|
122
|
+
self.root.grid_columnconfigure(0, weight=1)
|
|
123
|
+
self.main_frame.grid_rowconfigure(0, weight=1)
|
|
124
|
+
self.main_frame.grid_columnconfigure(0, weight=1)
|
|
125
|
+
|
|
126
|
+
# Create figure and axis
|
|
127
|
+
self.fig, self.ax = plt.subplots(figsize=(8, 6))
|
|
128
|
+
xlabel = 'X pixel (from 1 to NAXIS1)'
|
|
129
|
+
ylabel = 'Y pixel (from 1 to NAXIS2)'
|
|
130
|
+
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
131
|
+
self.image, _, _ = imshow(self.fig, self.ax, self.data, vmin=vmin, vmax=vmax,
|
|
132
|
+
xlabel=xlabel, ylabel=ylabel, extent=extent)
|
|
133
|
+
# Note: tight_layout should be called before defining the canvas
|
|
134
|
+
self.fig.tight_layout()
|
|
135
|
+
|
|
136
|
+
# Create canvas and toolbar
|
|
137
|
+
self.canvas = FigureCanvasTkAgg(self.fig, master=self.main_frame)
|
|
138
|
+
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
139
|
+
self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
|
|
140
|
+
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
141
|
+
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
142
|
+
canvas_widget = self.canvas.get_tk_widget()
|
|
143
|
+
canvas_widget.grid(row=0, column=0, sticky="nsew")
|
|
144
|
+
|
|
145
|
+
# Matplotlib toolbar
|
|
146
|
+
self.toolbar_frame = tk.Frame(self.main_frame)
|
|
147
|
+
self.toolbar_frame.grid(row=1, column=0, sticky="ew")
|
|
148
|
+
self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
|
|
149
|
+
self.toolbar.update()
|
|
150
|
+
|
|
151
|
+
def set_vmin(self):
|
|
152
|
+
old_vmin = self.get_vmin()
|
|
153
|
+
new_vmin = simpledialog.askfloat("Set vmin", "Enter new vmin:", initialvalue=old_vmin)
|
|
154
|
+
if new_vmin is None:
|
|
155
|
+
return
|
|
156
|
+
self.vmin_button.config(text=f"vmin: {new_vmin:.2f}")
|
|
157
|
+
self.image.set_clim(vmin=new_vmin)
|
|
158
|
+
self.canvas.draw()
|
|
159
|
+
|
|
160
|
+
def set_vmax(self):
|
|
161
|
+
old_vmax = self.get_vmax()
|
|
162
|
+
new_vmax = simpledialog.askfloat("Set vmax", "Enter new vmax:", initialvalue=old_vmax)
|
|
163
|
+
if new_vmax is None:
|
|
164
|
+
return
|
|
165
|
+
self.vmax_button.config(text=f"vmax: {new_vmax:.2f}")
|
|
166
|
+
self.image.set_clim(vmax=new_vmax)
|
|
167
|
+
self.canvas.draw()
|
|
168
|
+
|
|
169
|
+
def get_vmin(self):
|
|
170
|
+
return float(self.vmin_button.cget("text").split(":")[1])
|
|
171
|
+
|
|
172
|
+
def get_vmax(self):
|
|
173
|
+
return float(self.vmax_button.cget("text").split(":")[1])
|
|
174
|
+
|
|
175
|
+
def run_lacosmic(self):
|
|
176
|
+
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
177
|
+
self.stop_button.config(state=tk.DISABLED)
|
|
178
|
+
# Parameters for L.A.Cosmic can be adjusted as needed
|
|
179
|
+
_, mask_crfound = cosmicray_lacosmic(self.data, sigclip=4.5, sigfrac=0.3, objlim=5.0, verbose=True)
|
|
180
|
+
ReviewCosmicRay(
|
|
181
|
+
root=self.root,
|
|
182
|
+
data=self.data,
|
|
183
|
+
mask_fixed=self.mask_fixed,
|
|
184
|
+
mask_crfound=mask_crfound
|
|
185
|
+
)
|
|
186
|
+
print("L.A.Cosmic cleaning applied.")
|
|
187
|
+
self.run_lacosmic_button.config(state=tk.NORMAL)
|
|
188
|
+
self.stop_button.config(state=tk.NORMAL)
|
|
189
|
+
|
|
190
|
+
def stop_app(self):
|
|
191
|
+
self.root.quit()
|
|
192
|
+
self.root.destroy()
|
|
193
|
+
|
|
194
|
+
def on_key(self, event):
|
|
195
|
+
if event.key == 'q':
|
|
196
|
+
pass # Ignore the "q" key to prevent closing the window
|
|
197
|
+
else:
|
|
198
|
+
print(f"Key pressed: {event.key}")
|
|
199
|
+
|
|
200
|
+
def on_click(self, event):
|
|
201
|
+
if event.inaxes:
|
|
202
|
+
x, y = event.xdata, event.ydata
|
|
203
|
+
print(f"Clicked at image coordinates: ({x:.2f}, {y:.2f})")
|
|
@@ -7,25 +7,20 @@
|
|
|
7
7
|
# License-Filename: LICENSE.txt
|
|
8
8
|
#
|
|
9
9
|
|
|
10
|
-
"""
|
|
10
|
+
"""Define the ReviewCosmicRay class."""
|
|
11
11
|
|
|
12
|
-
import argparse
|
|
13
12
|
import tkinter as tk
|
|
14
|
-
from tkinter import filedialog
|
|
15
13
|
from tkinter import simpledialog
|
|
16
14
|
|
|
17
|
-
from astropy.io import fits
|
|
18
|
-
from ccdproc import cosmicray_lacosmic
|
|
19
15
|
import matplotlib.pyplot as plt
|
|
20
16
|
from matplotlib.backend_bases import key_press_handler
|
|
21
17
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
22
18
|
import numpy as np
|
|
23
|
-
import os
|
|
24
19
|
from scipy import ndimage
|
|
25
20
|
|
|
26
|
-
from
|
|
27
|
-
from
|
|
28
|
-
from
|
|
21
|
+
from ..imshow import imshow
|
|
22
|
+
from ..sliceregion import SliceRegion2D
|
|
23
|
+
from ..zscale import zscale
|
|
29
24
|
|
|
30
25
|
import matplotlib
|
|
31
26
|
matplotlib.use("TkAgg")
|
|
@@ -489,206 +484,3 @@ class ReviewCosmicRay():
|
|
|
489
484
|
self.remove_crosses_button.config(state=tk.NORMAL)
|
|
490
485
|
# Update the display to reflect the change
|
|
491
486
|
self.update_display()
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
class CosmicRayCleanerApp():
|
|
495
|
-
"""Main application class for cosmic ray cleaning."""
|
|
496
|
-
|
|
497
|
-
def __init__(self, root, input_fits, extension=0, output_fits=None):
|
|
498
|
-
"""
|
|
499
|
-
Initialize the application.
|
|
500
|
-
|
|
501
|
-
Parameters
|
|
502
|
-
----------
|
|
503
|
-
root : tk.Tk
|
|
504
|
-
The main Tkinter window.
|
|
505
|
-
input_fits : str
|
|
506
|
-
Path to the FITS file to be cleaned.
|
|
507
|
-
extension : int, optional
|
|
508
|
-
FITS extension to use (default is 0).
|
|
509
|
-
output_fits : str, optional
|
|
510
|
-
Path to save the cleaned FITS file (default is None, which prompts
|
|
511
|
-
for a save location).
|
|
512
|
-
"""
|
|
513
|
-
self.root = root
|
|
514
|
-
self.root.title("Cosmic Ray Cleaner")
|
|
515
|
-
self.root.geometry("800x700+50+0")
|
|
516
|
-
self.input_fits = input_fits
|
|
517
|
-
self.extension = extension
|
|
518
|
-
self.output_fits = output_fits
|
|
519
|
-
self.load_fits_file()
|
|
520
|
-
self.create_widgets()
|
|
521
|
-
|
|
522
|
-
def load_fits_file(self):
|
|
523
|
-
try:
|
|
524
|
-
with fits.open(self.input_fits, mode='readonly') as hdul:
|
|
525
|
-
self.data = hdul[self.extension].data
|
|
526
|
-
if 'CRMASK' in hdul:
|
|
527
|
-
self.mask_fixed = hdul['CRMASK'].data.astype(bool)
|
|
528
|
-
else:
|
|
529
|
-
self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
|
|
530
|
-
except Exception as e:
|
|
531
|
-
print(f"Error loading FITS file: {e}")
|
|
532
|
-
|
|
533
|
-
def save_fits_file(self):
|
|
534
|
-
if self.output_fits is None:
|
|
535
|
-
base, ext = os.path.splitext(self.input_fits)
|
|
536
|
-
suggested_name = f"{base}_cleaned"
|
|
537
|
-
else:
|
|
538
|
-
suggested_name, _ = os.path.splitext(self.output_fits)
|
|
539
|
-
self.output_fits = filedialog.asksaveasfilename(
|
|
540
|
-
initialdir=os.getcwd(),
|
|
541
|
-
title="Save cleaned FITS file",
|
|
542
|
-
defaultextension=".fits",
|
|
543
|
-
filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
|
|
544
|
-
initialfile=suggested_name
|
|
545
|
-
)
|
|
546
|
-
try:
|
|
547
|
-
with fits.open(self.input_fits, mode='readonly') as hdul:
|
|
548
|
-
hdul[self.extension].data = self.data
|
|
549
|
-
if 'CRMASK' in hdul:
|
|
550
|
-
hdul['CRMASK'].data = self.mask_fixed.astype(np.uint8)
|
|
551
|
-
else:
|
|
552
|
-
crmask_hdu = fits.ImageHDU(self.mask_fixed.astype(np.uint8), name='CRMASK')
|
|
553
|
-
hdul.append(crmask_hdu)
|
|
554
|
-
hdul.writeto(self.output_fits, overwrite=True)
|
|
555
|
-
print(f"Cleaned data saved to {self.output_fits}")
|
|
556
|
-
except Exception as e:
|
|
557
|
-
print(f"Error saving FITS file: {e}")
|
|
558
|
-
|
|
559
|
-
def create_widgets(self):
|
|
560
|
-
# Row 1
|
|
561
|
-
self.button_frame1 = tk.Frame(self.root)
|
|
562
|
-
self.button_frame1.grid(row=0, column=0, pady=5)
|
|
563
|
-
self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
|
|
564
|
-
self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
|
|
565
|
-
self.save_button = tk.Button(self.button_frame1, text="Save cleaned FITS", command=self.save_fits_file)
|
|
566
|
-
self.save_button.pack(side=tk.LEFT, padx=5)
|
|
567
|
-
|
|
568
|
-
# Row 2
|
|
569
|
-
self.button_frame2 = tk.Frame(self.root)
|
|
570
|
-
self.button_frame2.grid(row=1, column=0, pady=5)
|
|
571
|
-
vmin, vmax = zscale(self.data)
|
|
572
|
-
self.vmin_button = tk.Button(self.button_frame2, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
|
|
573
|
-
self.vmin_button.pack(side=tk.LEFT, padx=5)
|
|
574
|
-
self.vmax_button = tk.Button(self.button_frame2, text=f"vmax: {vmax:.2f}", command=self.set_vmax)
|
|
575
|
-
self.vmax_button.pack(side=tk.LEFT, padx=5)
|
|
576
|
-
self.stop_button = tk.Button(self.button_frame2, text="Stop program", command=self.stop_app)
|
|
577
|
-
self.stop_button.pack(side=tk.LEFT, padx=5)
|
|
578
|
-
|
|
579
|
-
# Main frame for figure and toolbar
|
|
580
|
-
self.main_frame = tk.Frame(self.root)
|
|
581
|
-
self.main_frame.grid(row=2, column=0, sticky="nsew")
|
|
582
|
-
self.root.grid_rowconfigure(2, weight=1)
|
|
583
|
-
self.root.grid_columnconfigure(0, weight=1)
|
|
584
|
-
self.main_frame.grid_rowconfigure(0, weight=1)
|
|
585
|
-
self.main_frame.grid_columnconfigure(0, weight=1)
|
|
586
|
-
|
|
587
|
-
# Create figure and axis
|
|
588
|
-
self.fig, self.ax = plt.subplots(figsize=(8, 6))
|
|
589
|
-
xlabel = 'X pixel (from 1 to NAXIS1)'
|
|
590
|
-
ylabel = 'Y pixel (from 1 to NAXIS2)'
|
|
591
|
-
extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
|
|
592
|
-
self.image, _, _ = imshow(self.fig, self.ax, self.data, vmin=vmin, vmax=vmax,
|
|
593
|
-
xlabel=xlabel, ylabel=ylabel, extent=extent)
|
|
594
|
-
# Note: tight_layout should be called before defining the canvas
|
|
595
|
-
self.fig.tight_layout()
|
|
596
|
-
|
|
597
|
-
# Create canvas and toolbar
|
|
598
|
-
self.canvas = FigureCanvasTkAgg(self.fig, master=self.main_frame)
|
|
599
|
-
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
600
|
-
self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
|
|
601
|
-
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
602
|
-
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
603
|
-
canvas_widget = self.canvas.get_tk_widget()
|
|
604
|
-
canvas_widget.grid(row=0, column=0, sticky="nsew")
|
|
605
|
-
|
|
606
|
-
# Matplotlib toolbar
|
|
607
|
-
self.toolbar_frame = tk.Frame(self.main_frame)
|
|
608
|
-
self.toolbar_frame.grid(row=1, column=0, sticky="ew")
|
|
609
|
-
self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
|
|
610
|
-
self.toolbar.update()
|
|
611
|
-
|
|
612
|
-
def set_vmin(self):
|
|
613
|
-
old_vmin = self.get_vmin()
|
|
614
|
-
new_vmin = simpledialog.askfloat("Set vmin", "Enter new vmin:", initialvalue=old_vmin)
|
|
615
|
-
if new_vmin is None:
|
|
616
|
-
return
|
|
617
|
-
self.vmin_button.config(text=f"vmin: {new_vmin:.2f}")
|
|
618
|
-
self.image.set_clim(vmin=new_vmin)
|
|
619
|
-
self.canvas.draw()
|
|
620
|
-
|
|
621
|
-
def set_vmax(self):
|
|
622
|
-
old_vmax = self.get_vmax()
|
|
623
|
-
new_vmax = simpledialog.askfloat("Set vmax", "Enter new vmax:", initialvalue=old_vmax)
|
|
624
|
-
if new_vmax is None:
|
|
625
|
-
return
|
|
626
|
-
self.vmax_button.config(text=f"vmax: {new_vmax:.2f}")
|
|
627
|
-
self.image.set_clim(vmax=new_vmax)
|
|
628
|
-
self.canvas.draw()
|
|
629
|
-
|
|
630
|
-
def get_vmin(self):
|
|
631
|
-
return float(self.vmin_button.cget("text").split(":")[1])
|
|
632
|
-
|
|
633
|
-
def get_vmax(self):
|
|
634
|
-
return float(self.vmax_button.cget("text").split(":")[1])
|
|
635
|
-
|
|
636
|
-
def run_lacosmic(self):
|
|
637
|
-
self.run_lacosmic_button.config(state=tk.DISABLED)
|
|
638
|
-
self.stop_button.config(state=tk.DISABLED)
|
|
639
|
-
# Parameters for L.A.Cosmic can be adjusted as needed
|
|
640
|
-
_, mask_crfound = cosmicray_lacosmic(self.data, sigclip=4.5, sigfrac=0.3, objlim=5.0, verbose=True)
|
|
641
|
-
ReviewCosmicRay(
|
|
642
|
-
root=self.root,
|
|
643
|
-
data=self.data,
|
|
644
|
-
mask_fixed=self.mask_fixed,
|
|
645
|
-
mask_crfound=mask_crfound
|
|
646
|
-
)
|
|
647
|
-
print("L.A.Cosmic cleaning applied.")
|
|
648
|
-
self.run_lacosmic_button.config(state=tk.NORMAL)
|
|
649
|
-
self.stop_button.config(state=tk.NORMAL)
|
|
650
|
-
|
|
651
|
-
def stop_app(self):
|
|
652
|
-
self.root.quit()
|
|
653
|
-
self.root.destroy()
|
|
654
|
-
|
|
655
|
-
def on_key(self, event):
|
|
656
|
-
if event.key == 'q':
|
|
657
|
-
pass # Ignore the "q" key to prevent closing the window
|
|
658
|
-
else:
|
|
659
|
-
print(f"Key pressed: {event.key}")
|
|
660
|
-
|
|
661
|
-
def on_click(self, event):
|
|
662
|
-
if event.inaxes:
|
|
663
|
-
x, y = event.xdata, event.ydata
|
|
664
|
-
print(f"Clicked at image coordinates: ({x:.2f}, {y:.2f})")
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
def main():
|
|
668
|
-
parser = argparse.ArgumentParser(description="Interactive cosmic ray cleaner for FITS images.")
|
|
669
|
-
parser.add_argument("input_fits", help="Path to the FITS file to be cleaned.")
|
|
670
|
-
parser.add_argument("--extension", type=int, default=0,
|
|
671
|
-
help="FITS extension to use (default: 0).")
|
|
672
|
-
parser.add_argument("--output_fits", type=str, default=None,
|
|
673
|
-
help="Path to save the cleaned FITS file")
|
|
674
|
-
args = parser.parse_args()
|
|
675
|
-
|
|
676
|
-
if not os.path.isfile(args.input_fits):
|
|
677
|
-
print(f"Error: File '{args.input_fits}' does not exist.")
|
|
678
|
-
return
|
|
679
|
-
if args.output_fits is not None and os.path.isfile(args.output_fits):
|
|
680
|
-
print(f"Error: Output file '{args.output_fits}' already exists.")
|
|
681
|
-
return
|
|
682
|
-
|
|
683
|
-
# Initialize Tkinter root
|
|
684
|
-
root = tk.Tk()
|
|
685
|
-
|
|
686
|
-
# Create and run the application
|
|
687
|
-
CosmicRayCleanerApp(root, args.input_fits, args.extension, args.output_fits)
|
|
688
|
-
|
|
689
|
-
# Execute
|
|
690
|
-
root.mainloop()
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
if __name__ == "__main__":
|
|
694
|
-
main()
|
|
File without changes
|
|
@@ -0,0 +1,34 @@
|
|
|
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
|
+
import os
|
|
10
|
+
import requests
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def get_cookbook_file(file_path, verbose=True):
|
|
14
|
+
"""
|
|
15
|
+
Retrieve the contents of an auxiliary file.
|
|
16
|
+
|
|
17
|
+
The file is downloaded from the teareduce-cookbook GitHub repository
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
file_path : str
|
|
22
|
+
The path to the auxiliary file.
|
|
23
|
+
verbose : bool, optional
|
|
24
|
+
If True, print a message when the file is downloaded.
|
|
25
|
+
Default is True.
|
|
26
|
+
"""
|
|
27
|
+
|
|
28
|
+
url = f"https://raw.githubusercontent.com/nicocardiel/teareduce-cookbook/main/{file_path}"
|
|
29
|
+
response = requests.get(url)
|
|
30
|
+
local_filename = os.path.basename(file_path)
|
|
31
|
+
with open(local_filename, "wb") as f:
|
|
32
|
+
f.write(response.content)
|
|
33
|
+
if verbose:
|
|
34
|
+
print(f"File {local_filename} downloaded!")
|
teareduce/imshow.py
CHANGED
|
@@ -14,13 +14,18 @@ from matplotlib.axes import Axes
|
|
|
14
14
|
from mpl_toolkits.axes_grid1 import make_axes_locatable
|
|
15
15
|
|
|
16
16
|
|
|
17
|
-
def imshowme(data, **kwargs):
|
|
17
|
+
def imshowme(data, return_objs=False, **kwargs):
|
|
18
18
|
"""Simple execution of teareduce.imshow.
|
|
19
19
|
|
|
20
20
|
Parameters
|
|
21
21
|
----------
|
|
22
22
|
data : numpy.ndarray
|
|
23
23
|
2D array to be displayed
|
|
24
|
+
return_objs : bool
|
|
25
|
+
If True, return the Figure, Axes, AxesImage,
|
|
26
|
+
color bar Axes and Colorbar instances.
|
|
27
|
+
**kwargs : dict
|
|
28
|
+
Additional parameters passed to imshow().
|
|
24
29
|
|
|
25
30
|
Returns
|
|
26
31
|
-------
|
|
@@ -37,14 +42,16 @@ def imshowme(data, **kwargs):
|
|
|
37
42
|
Instance of Colorbar, or None if colorbar is False.
|
|
38
43
|
"""
|
|
39
44
|
fig, ax = plt.subplots()
|
|
40
|
-
img, cax, cbar = imshow(fig=fig, ax=ax, data=data, **kwargs)
|
|
41
|
-
|
|
45
|
+
img, cax, cbar = imshow(fig=fig, ax=ax, data=data, return_objs=True, **kwargs)
|
|
46
|
+
if return_objs:
|
|
47
|
+
return fig, ax, img, cax, cbar
|
|
42
48
|
|
|
43
49
|
|
|
44
50
|
def imshow(fig=None, ax=None, data=None, ds9mode=False,
|
|
45
51
|
crpix1=1, crval1=None, cdelt1=None, cunit1=None, cunitx=Unit('Angstrom'),
|
|
46
52
|
xlabel=None, ylabel=None, title=None,
|
|
47
53
|
colorbar=True, cblabel='Number of counts',
|
|
54
|
+
return_objs=True,
|
|
48
55
|
**kwargs):
|
|
49
56
|
"""Call imshow() with color bar and default labels.
|
|
50
57
|
|
|
@@ -89,6 +96,11 @@ def imshow(fig=None, ax=None, data=None, ds9mode=False,
|
|
|
89
96
|
If True, display color bar.
|
|
90
97
|
cblabel : str
|
|
91
98
|
Color bar label.
|
|
99
|
+
return_objs : bool
|
|
100
|
+
If True, return AxesImage, color bar Axes and Colorbar
|
|
101
|
+
instances.
|
|
102
|
+
**kwargs : dict
|
|
103
|
+
Additional parameters passed to imshow().
|
|
92
104
|
|
|
93
105
|
Return
|
|
94
106
|
------
|
|
@@ -188,4 +200,5 @@ def imshow(fig=None, ax=None, data=None, ds9mode=False,
|
|
|
188
200
|
cax = None
|
|
189
201
|
cbar = None
|
|
190
202
|
|
|
191
|
-
|
|
203
|
+
if return_objs:
|
|
204
|
+
return img, cax, cbar
|
teareduce/sliceregion.py
CHANGED
|
@@ -85,12 +85,12 @@ class SliceRegion1D:
|
|
|
85
85
|
|
|
86
86
|
if self.mode == 'fits':
|
|
87
87
|
if region.stop < region.start:
|
|
88
|
-
raise ValueError(f'Invalid {region!r}')
|
|
88
|
+
raise ValueError(f'Invalid {region!r}: stop must be >= start')
|
|
89
89
|
self.fits = region
|
|
90
90
|
self.python = slice(region.start-1, region.stop)
|
|
91
91
|
elif self.mode == 'python':
|
|
92
92
|
if region.stop <= region.start:
|
|
93
|
-
raise ValueError(f'Invalid {region!r}')
|
|
93
|
+
raise ValueError(f'Invalid {region!r}: stop must be > start')
|
|
94
94
|
self.fits = slice(region.start+1, region.stop)
|
|
95
95
|
self.python = region
|
|
96
96
|
else:
|
|
@@ -227,16 +227,16 @@ class SliceRegion2D:
|
|
|
227
227
|
|
|
228
228
|
if self.mode == 'fits':
|
|
229
229
|
if s1.stop < s1.start:
|
|
230
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
230
|
+
raise ValueError(f'Invalid {s1!r}: stop must be >= start')
|
|
231
231
|
if s2.stop < s2.start:
|
|
232
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
232
|
+
raise ValueError(f'Invalid {s2!r}: stop must be >= start')
|
|
233
233
|
self.fits = region
|
|
234
234
|
self.python = slice(s2.start-1, s2.stop), slice(s1.start-1, s1.stop)
|
|
235
235
|
elif self.mode == 'python':
|
|
236
236
|
if s1.stop <= s1.start:
|
|
237
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
237
|
+
raise ValueError(f'Invalid {s1!r}: stop must be > start')
|
|
238
238
|
if s2.stop <= s2.start:
|
|
239
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
239
|
+
raise ValueError(f'Invalid {s2!r}: stop must be > start')
|
|
240
240
|
self.fits = slice(s2.start+1, s2.stop), slice(s1.start+1, s1.stop)
|
|
241
241
|
self.python = region
|
|
242
242
|
else:
|
|
@@ -387,20 +387,20 @@ class SliceRegion3D:
|
|
|
387
387
|
|
|
388
388
|
if self.mode == 'fits':
|
|
389
389
|
if s1.stop < s1.start:
|
|
390
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
390
|
+
raise ValueError(f'Invalid {s1!r}: stop must be >= start')
|
|
391
391
|
if s2.stop < s2.start:
|
|
392
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
392
|
+
raise ValueError(f'Invalid {s2!r}: stop must be >= start')
|
|
393
393
|
if s3.stop < s3.start:
|
|
394
|
-
raise ValueError(f'Invalid {s3!r}')
|
|
394
|
+
raise ValueError(f'Invalid {s3!r}: stop must be >= start')
|
|
395
395
|
self.fits = region
|
|
396
396
|
self.python = slice(s3.start-1, s3.stop), slice(s2.start-1, s2.stop), slice(s1.start-1, s1.stop)
|
|
397
397
|
elif self.mode == 'python':
|
|
398
398
|
if s1.stop <= s1.start:
|
|
399
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
399
|
+
raise ValueError(f'Invalid {s1!r}: stop must be > start')
|
|
400
400
|
if s2.stop <= s2.start:
|
|
401
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
401
|
+
raise ValueError(f'Invalid {s2!r}: stop must be > start')
|
|
402
402
|
if s3.stop <= s3.start:
|
|
403
|
-
raise ValueError(f'Invalid {s3!r}')
|
|
403
|
+
raise ValueError(f'Invalid {s3!r}: stop must be > start')
|
|
404
404
|
self.fits = slice(s3.start+1, s3.stop), slice(s2.start+1, s2.stop), slice(s1.start+1, s1.stop)
|
|
405
405
|
self.python = region
|
|
406
406
|
else:
|
teareduce/version.py
CHANGED
|
@@ -1,31 +1,36 @@
|
|
|
1
|
-
teareduce/__init__.py,sha256=
|
|
1
|
+
teareduce/__init__.py,sha256=TkPRzPxHYcnfZtBX2hkA8bP5V7rjUlP7iGx-0L4vOmg,1362
|
|
2
2
|
teareduce/avoid_astropy_warnings.py,sha256=2YgQ47pxsKYWDxUtzyEOfh3Is3aAHHmjJkuOa1JCDN4,648
|
|
3
|
-
teareduce/cleanest.py,sha256=F5FXrTsMSb0EG7Om6izrhyNn_d0JKKSXphoftwd-_1k,30865
|
|
4
3
|
teareduce/correct_pincushion_distortion.py,sha256=Xpt03jtmJMyqik4ta95zMRE3Z6dVfzzHI2z5IDbtnMk,1685
|
|
5
4
|
teareduce/cosmicrays.py,sha256=gLHgq9LdfNHmZ5n_FYxvxcGI2245TXGV-rFHSIogxPE,26877
|
|
6
5
|
teareduce/ctext.py,sha256=cCVVsFRQ73V0JwDCytd73U3HWRjfv5xkt3ca5-YWuwo,2081
|
|
7
6
|
teareduce/draw_rectangle.py,sha256=mKZ0j8ZRtbxkZH9pQjja_xgP-LSpfe9c7YTgHJX_nSc,1911
|
|
8
7
|
teareduce/elapsed_time.py,sha256=QWakPeiOUA__WpjjFREnodKqW1FZzVGWstab0N3Ro6k,1418
|
|
9
8
|
teareduce/histogram1d.py,sha256=3hlvcI8XPRmZ27H_sFmyL26gIcbJozk07ELBKWtngQk,2835
|
|
10
|
-
teareduce/imshow.py,sha256=
|
|
9
|
+
teareduce/imshow.py,sha256=qnic8X0GTjHW5yIITEZcqZmefzDpQlGJH7v2bPHI6t8,7082
|
|
11
10
|
teareduce/numsplines.py,sha256=1PpG-frdc9Qz3VRbC7XyZFWKmhus05ID4REtFnWDmUo,8049
|
|
12
11
|
teareduce/peaks_spectrum.py,sha256=XLMbJZnyYDVSaa5nCMXk9kSn0fyG_lhVmXWHzTwehiA,9878
|
|
13
12
|
teareduce/polfit.py,sha256=CGsrRsz_Du2aKxOcgXi36lpAZO04JyqCCUaxhC0C-Mk,14281
|
|
14
13
|
teareduce/robust_std.py,sha256=dk1G3VgiplZzmSfL7qWniEZ-5c3novHnBpRPCM77u84,1084
|
|
15
14
|
teareduce/sdistortion.py,sha256=5ZsZn4vD5Sw2aoqO8-NIOH7H89Zmh7ZDkow6YbAotHU,5916
|
|
16
15
|
teareduce/simulateccdexposure.py,sha256=cdbpca6GVuM3d7R1LGzlIZZvjTq_jzrlkk_Cli7aouQ,24636
|
|
17
|
-
teareduce/sliceregion.py,sha256=
|
|
16
|
+
teareduce/sliceregion.py,sha256=Jdf8XvmGaY_vaY1cneTaRtSOYPxpUsJm9cXJDDMa0YM,18626
|
|
18
17
|
teareduce/statsummary.py,sha256=VTNAnBV8z6suqiyB2Lhw3YjUUOjlmwUPX3enkOKRF54,5422
|
|
19
|
-
teareduce/version.py,sha256=
|
|
18
|
+
teareduce/version.py,sha256=wcSjl385CD5AkqC44ZOXcxI-nn2bOzLYHTz2gsT753c,419
|
|
20
19
|
teareduce/wavecal.py,sha256=2MewWz5Y3ms41c305UtWOM_LaLNdk1mugDXCtzf-fSw,68586
|
|
21
20
|
teareduce/write_array_to_fits.py,sha256=kWDrEH9coJ1yIu56oQJpWtDqJL4c8HGmssE9jle4e94,617
|
|
22
21
|
teareduce/zscale.py,sha256=SDgmcDD2N5GvDn46JADCgTQJPBF_N_jSKMHoeTz9Nsw,1152
|
|
22
|
+
teareduce/cleanest/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
23
|
+
teareduce/cleanest/__main__.py,sha256=DGr32TeUa2gsiokQzNXkYrOCJcLWoE0BRQACKq20xfg,1371
|
|
24
|
+
teareduce/cleanest/cosmicraycleanerapp.py,sha256=qE8Wq6j240Pzfd5DB8mR579qnYt359yN0Lj5FZeFp4s,7976
|
|
25
|
+
teareduce/cleanest/reviewcosmicray.py,sha256=ha3MeSfT-RvSQnRSq8_M59JfnOGNd3IgtyHklrujE08,22450
|
|
26
|
+
teareduce/cookbook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
|
+
teareduce/cookbook/get_cookbook_file.py,sha256=n40HoWv3eiFtaquYWTAM6CbVfha9oUO9LHmnU2a5jkc,902
|
|
23
28
|
teareduce/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
24
29
|
teareduce/tests/test_sliceregion.py,sha256=S7Zoh2eEBFIEbfsXgWBEMCf7pottjw2oLhqlZJQkAwg,3785
|
|
25
30
|
teareduce/tests/test_version.py,sha256=mKLnbXyvVNc1pATq5PxR8qeoFMPAFL_GilFV6IHLOi0,172
|
|
26
|
-
teareduce-0.4.
|
|
27
|
-
teareduce-0.4.
|
|
28
|
-
teareduce-0.4.
|
|
29
|
-
teareduce-0.4.
|
|
30
|
-
teareduce-0.4.
|
|
31
|
-
teareduce-0.4.
|
|
31
|
+
teareduce-0.4.9.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
32
|
+
teareduce-0.4.9.dist-info/METADATA,sha256=4N3TttqfbtFwlNcHNzRPZotpOp1ZKLkD9Nrtrx7tYpM,3017
|
|
33
|
+
teareduce-0.4.9.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
teareduce-0.4.9.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
|
|
35
|
+
teareduce-0.4.9.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
|
|
36
|
+
teareduce-0.4.9.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|