teareduce 0.4.6__py3-none-any.whl → 0.4.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/__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/sliceregion.py +85 -15
- teareduce/tests/test_sliceregion.py +67 -0
- teareduce/version.py +1 -1
- {teareduce-0.4.6.dist-info → teareduce-0.4.8.dist-info}/METADATA +1 -1
- {teareduce-0.4.6.dist-info → teareduce-0.4.8.dist-info}/RECORD +16 -11
- teareduce-0.4.8.dist-info/entry_points.txt +2 -0
- teareduce-0.4.6.dist-info/entry_points.txt +0 -2
- {teareduce-0.4.6.dist-info → teareduce-0.4.8.dist-info}/WHEEL +0 -0
- {teareduce-0.4.6.dist-info → teareduce-0.4.8.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.4.6.dist-info → teareduce-0.4.8.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/sliceregion.py
CHANGED
|
@@ -43,7 +43,7 @@ class SliceRegion1D:
|
|
|
43
43
|
Check if slice 'other' is within the parent slice.
|
|
44
44
|
"""
|
|
45
45
|
|
|
46
|
-
def __init__(self, region, mode=None):
|
|
46
|
+
def __init__(self, region, mode=None, naxis1=None):
|
|
47
47
|
"""Initialize SliceRegion1D.
|
|
48
48
|
|
|
49
49
|
Parameters
|
|
@@ -54,6 +54,9 @@ class SliceRegion1D:
|
|
|
54
54
|
mode : str
|
|
55
55
|
Convention mode employed to define the slice.
|
|
56
56
|
The two possible modes are 'fits' and 'python'.
|
|
57
|
+
naxis1 : int
|
|
58
|
+
The axis 1 size (length) of the data being sliced.
|
|
59
|
+
If provided, it is used to validate the slice region.
|
|
57
60
|
"""
|
|
58
61
|
if isinstance(region, str):
|
|
59
62
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -82,17 +85,23 @@ class SliceRegion1D:
|
|
|
82
85
|
|
|
83
86
|
if self.mode == 'fits':
|
|
84
87
|
if region.stop < region.start:
|
|
85
|
-
raise ValueError(f'Invalid {region!r}')
|
|
88
|
+
raise ValueError(f'Invalid {region!r}: stop must be >= start')
|
|
86
89
|
self.fits = region
|
|
87
90
|
self.python = slice(region.start-1, region.stop)
|
|
88
91
|
elif self.mode == 'python':
|
|
89
92
|
if region.stop <= region.start:
|
|
90
|
-
raise ValueError(f'Invalid {region!r}')
|
|
93
|
+
raise ValueError(f'Invalid {region!r}: stop must be > start')
|
|
91
94
|
self.fits = slice(region.start+1, region.stop)
|
|
92
95
|
self.python = region
|
|
93
96
|
else:
|
|
94
97
|
raise ValueError(errmsg)
|
|
95
98
|
|
|
99
|
+
if naxis1 is not None:
|
|
100
|
+
if not (1 <= self.fits.start <= naxis1):
|
|
101
|
+
raise ValueError(f'Invalid start={self.fits.start} for naxis1={naxis1}')
|
|
102
|
+
if not (1 <= self.fits.stop <= naxis1+1):
|
|
103
|
+
raise ValueError(f'Invalid stop={self.fits.stop} for naxis1={naxis1}')
|
|
104
|
+
|
|
96
105
|
s = self.fits
|
|
97
106
|
self.fits_section = f'[{s.start}:{s.stop}]'
|
|
98
107
|
|
|
@@ -137,6 +146,10 @@ class SliceRegion1D:
|
|
|
137
146
|
result = True
|
|
138
147
|
return result
|
|
139
148
|
|
|
149
|
+
def length(self):
|
|
150
|
+
"""Return the length of the slice."""
|
|
151
|
+
return self.python.stop - self.python.start
|
|
152
|
+
|
|
140
153
|
|
|
141
154
|
class SliceRegion2D:
|
|
142
155
|
"""Store indices for slicing of 2D regions.
|
|
@@ -163,7 +176,7 @@ class SliceRegion2D:
|
|
|
163
176
|
within(other)
|
|
164
177
|
Check if slice 'other' is within the parent slice."""
|
|
165
178
|
|
|
166
|
-
def __init__(self, region, mode=None):
|
|
179
|
+
def __init__(self, region, mode=None, naxis1=None, naxis2=None):
|
|
167
180
|
"""Initialize SliceRegion2D.
|
|
168
181
|
|
|
169
182
|
Parameters
|
|
@@ -175,6 +188,14 @@ class SliceRegion2D:
|
|
|
175
188
|
mode : str
|
|
176
189
|
Convention mode employed to define the slice.
|
|
177
190
|
The two possible modes are 'fits' and 'python'.
|
|
191
|
+
naxis1 : int
|
|
192
|
+
The axis 1 size (length) of the data being sliced,
|
|
193
|
+
assuming the FITS convention.
|
|
194
|
+
If provided, it is used to validate the slice region.
|
|
195
|
+
naxis2 : int
|
|
196
|
+
The axis 2 size (length) of the data being sliced,
|
|
197
|
+
assuming the FITS convention.
|
|
198
|
+
If provided, it is used to validate the slice region.
|
|
178
199
|
"""
|
|
179
200
|
if isinstance(region, str):
|
|
180
201
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -206,16 +227,16 @@ class SliceRegion2D:
|
|
|
206
227
|
|
|
207
228
|
if self.mode == 'fits':
|
|
208
229
|
if s1.stop < s1.start:
|
|
209
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
230
|
+
raise ValueError(f'Invalid {s1!r}: stop must be >= start')
|
|
210
231
|
if s2.stop < s2.start:
|
|
211
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
232
|
+
raise ValueError(f'Invalid {s2!r}: stop must be >= start')
|
|
212
233
|
self.fits = region
|
|
213
234
|
self.python = slice(s2.start-1, s2.stop), slice(s1.start-1, s1.stop)
|
|
214
235
|
elif self.mode == 'python':
|
|
215
236
|
if s1.stop <= s1.start:
|
|
216
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
237
|
+
raise ValueError(f'Invalid {s1!r}: stop must be > start')
|
|
217
238
|
if s2.stop <= s2.start:
|
|
218
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
239
|
+
raise ValueError(f'Invalid {s2!r}: stop must be > start')
|
|
219
240
|
self.fits = slice(s2.start+1, s2.stop), slice(s1.start+1, s1.stop)
|
|
220
241
|
self.python = region
|
|
221
242
|
else:
|
|
@@ -224,6 +245,17 @@ class SliceRegion2D:
|
|
|
224
245
|
s1, s2 = self.fits
|
|
225
246
|
self.fits_section = f'[{s1.start}:{s1.stop},{s2.start}:{s2.stop}]'
|
|
226
247
|
|
|
248
|
+
if naxis1 is not None:
|
|
249
|
+
if not (1 <= s1.start <= naxis1):
|
|
250
|
+
raise ValueError(f'Invalid start={s1.start} for naxis1={naxis1}')
|
|
251
|
+
if not (1 <= s1.stop <= naxis1):
|
|
252
|
+
raise ValueError(f'Invalid stop={s1.stop} for naxis1={naxis1}')
|
|
253
|
+
if naxis2 is not None:
|
|
254
|
+
if not (1 <= s2.start <= naxis2):
|
|
255
|
+
raise ValueError(f'Invalid start={s2.start} for naxis2={naxis2}')
|
|
256
|
+
if not (1 <= s2.stop <= naxis2):
|
|
257
|
+
raise ValueError(f'Invalid stop={s2.stop} for naxis2={naxis2}')
|
|
258
|
+
|
|
227
259
|
def __eq__(self, other):
|
|
228
260
|
return self.fits == other.fits and self.python == other.python
|
|
229
261
|
|
|
@@ -269,6 +301,11 @@ class SliceRegion2D:
|
|
|
269
301
|
result = True
|
|
270
302
|
return result
|
|
271
303
|
|
|
304
|
+
def area(self):
|
|
305
|
+
"""Return the area of the slice."""
|
|
306
|
+
s1, s2 = self.python
|
|
307
|
+
return (s1.stop - s1.start) * (s2.stop - s2.start)
|
|
308
|
+
|
|
272
309
|
|
|
273
310
|
class SliceRegion3D:
|
|
274
311
|
"""Store indices for slicing of 3D regions.
|
|
@@ -295,7 +332,7 @@ class SliceRegion3D:
|
|
|
295
332
|
within(other)
|
|
296
333
|
Check if slice 'other' is within the parent slice."""
|
|
297
334
|
|
|
298
|
-
def __init__(self, region, mode=None):
|
|
335
|
+
def __init__(self, region, mode=None, naxis1=None, naxis2=None, naxis3=None):
|
|
299
336
|
"""Initialize SliceRegion3D.
|
|
300
337
|
|
|
301
338
|
Parameters
|
|
@@ -307,6 +344,18 @@ class SliceRegion3D:
|
|
|
307
344
|
mode : str
|
|
308
345
|
Convention mode employed to define the slice.
|
|
309
346
|
The two possible modes are 'fits' and 'python'.
|
|
347
|
+
naxis1 : int
|
|
348
|
+
The axis 1 size (length) of the data being sliced,
|
|
349
|
+
assuming the FITS convention.
|
|
350
|
+
If provided, it is used to validate the slice region.
|
|
351
|
+
naxis2 : int
|
|
352
|
+
The axis 2 size (length) of the data being sliced,
|
|
353
|
+
assuming the FITS convention.
|
|
354
|
+
If provided, it is used to validate the slice region.
|
|
355
|
+
naxis3 : int
|
|
356
|
+
The axis 3 size (length) of the data being sliced,
|
|
357
|
+
assuming the FITS convention.
|
|
358
|
+
If provided, it is used to validate the slice region.
|
|
310
359
|
"""
|
|
311
360
|
if isinstance(region, str):
|
|
312
361
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -338,20 +387,20 @@ class SliceRegion3D:
|
|
|
338
387
|
|
|
339
388
|
if self.mode == 'fits':
|
|
340
389
|
if s1.stop < s1.start:
|
|
341
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
390
|
+
raise ValueError(f'Invalid {s1!r}: stop must be >= start')
|
|
342
391
|
if s2.stop < s2.start:
|
|
343
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
392
|
+
raise ValueError(f'Invalid {s2!r}: stop must be >= start')
|
|
344
393
|
if s3.stop < s3.start:
|
|
345
|
-
raise ValueError(f'Invalid {s3!r}')
|
|
394
|
+
raise ValueError(f'Invalid {s3!r}: stop must be >= start')
|
|
346
395
|
self.fits = region
|
|
347
396
|
self.python = slice(s3.start-1, s3.stop), slice(s2.start-1, s2.stop), slice(s1.start-1, s1.stop)
|
|
348
397
|
elif self.mode == 'python':
|
|
349
398
|
if s1.stop <= s1.start:
|
|
350
|
-
raise ValueError(f'Invalid {s1!r}')
|
|
399
|
+
raise ValueError(f'Invalid {s1!r}: stop must be > start')
|
|
351
400
|
if s2.stop <= s2.start:
|
|
352
|
-
raise ValueError(f'Invalid {s2!r}')
|
|
401
|
+
raise ValueError(f'Invalid {s2!r}: stop must be > start')
|
|
353
402
|
if s3.stop <= s3.start:
|
|
354
|
-
raise ValueError(f'Invalid {s3!r}')
|
|
403
|
+
raise ValueError(f'Invalid {s3!r}: stop must be > start')
|
|
355
404
|
self.fits = slice(s3.start+1, s3.stop), slice(s2.start+1, s2.stop), slice(s1.start+1, s1.stop)
|
|
356
405
|
self.python = region
|
|
357
406
|
else:
|
|
@@ -360,6 +409,22 @@ class SliceRegion3D:
|
|
|
360
409
|
s1, s2, s3 = self.fits
|
|
361
410
|
self.fits_section = f'[{s1.start}:{s1.stop},{s2.start}:{s2.stop},{s3.start}:{s3.stop}]'
|
|
362
411
|
|
|
412
|
+
if naxis1 is not None:
|
|
413
|
+
if not (1 <= s1.start <= naxis1):
|
|
414
|
+
raise ValueError(f'Invalid start={s1.start} for naxis1={naxis1}')
|
|
415
|
+
if not (1 <= s1.stop <= naxis1):
|
|
416
|
+
raise ValueError(f'Invalid stop={s1.stop} for naxis1={naxis1}')
|
|
417
|
+
if naxis2 is not None:
|
|
418
|
+
if not (1 <= s2.start <= naxis2):
|
|
419
|
+
raise ValueError(f'Invalid start={s2.start} for naxis2={naxis2}')
|
|
420
|
+
if not (1 <= s2.stop <= naxis2):
|
|
421
|
+
raise ValueError(f'Invalid stop={s2.stop} for naxis2={naxis2}')
|
|
422
|
+
if naxis3 is not None:
|
|
423
|
+
if not (1 <= s3.start <= naxis3):
|
|
424
|
+
raise ValueError(f'Invalid start={s3.start} for naxis3={naxis3}')
|
|
425
|
+
if not (1 <= s3.stop <= naxis3):
|
|
426
|
+
raise ValueError(f'Invalid stop={s3.stop} for naxis3={naxis3}')
|
|
427
|
+
|
|
363
428
|
def __eq__(self, other):
|
|
364
429
|
return self.fits == other.fits and self.python == other.python
|
|
365
430
|
|
|
@@ -408,3 +473,8 @@ class SliceRegion3D:
|
|
|
408
473
|
return result
|
|
409
474
|
result = True
|
|
410
475
|
return result
|
|
476
|
+
|
|
477
|
+
def volume(self):
|
|
478
|
+
"""Return the volume of the slice."""
|
|
479
|
+
s1, s2, s3 = self.python
|
|
480
|
+
return (s1.stop - s1.start) * (s2.stop - s2.start) * (s3.stop - s3.start)
|
|
@@ -32,6 +32,7 @@ def test_slice_region_creation():
|
|
|
32
32
|
assert region3d.python == (slice(1, 10, None), slice(2, 20, None), slice(3, 30, None))
|
|
33
33
|
assert region3d.fits_section == '[4:30,3:20,2:10]'
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
def test_slice_values():
|
|
36
37
|
"""Test the values of the slices in different modes."""
|
|
37
38
|
|
|
@@ -47,3 +48,69 @@ def test_slice_values():
|
|
|
47
48
|
array3d = np.arange(24).reshape(3, 4, 2)
|
|
48
49
|
region3d = SliceRegion3D(np.s_[1:3, 2:4, 1:2], mode='python')
|
|
49
50
|
assert np.all(array3d[region3d.python] == np.array([[[13], [15]], [[21], [23]]]))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_wrong_number_of_dimensions():
|
|
54
|
+
"""Test the creation of a SliceRegion with wrong slices."""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
SliceRegion1D(np.s_[1:3, 2:4], mode='python')
|
|
58
|
+
assert False, "Expected ValueError for 1D slice with 2D input"
|
|
59
|
+
except ValueError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
SliceRegion2D(np.s_[1:3], mode='python')
|
|
64
|
+
assert False, "Expected ValueError for 2D slice with 1D input"
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
SliceRegion3D(np.s_[1:3, 2:4], mode='python')
|
|
70
|
+
assert False, "Expected ValueError for 3D slice with 2D input"
|
|
71
|
+
except ValueError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_wrong_limits_order():
|
|
76
|
+
"""Test the creation of a SliceRegion with wrong limits."""
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
SliceRegion1D(np.s_[10:5], mode='python')
|
|
80
|
+
assert False, "Expected ValueError for 1D slice with start > stop"
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
SliceRegion2D(np.s_[1:3, 5:2], mode='python')
|
|
86
|
+
assert False, "Expected ValueError for 2D slice with start > stop in second dimension"
|
|
87
|
+
except ValueError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
SliceRegion3D(np.s_[1:3, 2:4, 6:1], mode='python')
|
|
92
|
+
assert False, "Expected ValueError for 3D slice with start > stop in third dimension"
|
|
93
|
+
except ValueError:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_limits_out_of_range():
|
|
98
|
+
"""Test the creation of a SliceRegion with limits out of range."""
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
SliceRegion1D(np.s_[-1:5], mode='python', naxis1=10)
|
|
102
|
+
assert False, "Expected ValueError for 1D slice with negative start"
|
|
103
|
+
except ValueError:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
SliceRegion2D(np.s_[1:3, 2:25], mode='python', naxis1=10, naxis2=20)
|
|
108
|
+
assert False, "Expected ValueError for 2D slice with stop > naxis in second dimension"
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
SliceRegion3D(np.s_[1:3, 2:4, 3:35], mode='python', naxis1=10, naxis2=20, naxis3=30)
|
|
114
|
+
assert False, "Expected ValueError for 3D slice with stop > naxis in third dimension"
|
|
115
|
+
except ValueError:
|
|
116
|
+
pass
|
teareduce/version.py
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
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
|
|
@@ -14,18 +13,24 @@ 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=9bRTfbP-0J-zaWu-g_Yo1f-fV-a__5oRYcf981NnIE0,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
|
-
teareduce/tests/test_sliceregion.py,sha256=
|
|
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.8.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
|
|
32
|
+
teareduce-0.4.8.dist-info/METADATA,sha256=jW69XhMxJd0YFBNEZnf6OvSmuiVswWzEsS_jRHgqRiY,3017
|
|
33
|
+
teareduce-0.4.8.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
|
|
34
|
+
teareduce-0.4.8.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
|
|
35
|
+
teareduce-0.4.8.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
|
|
36
|
+
teareduce-0.4.8.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|