teareduce 0.6.8__tar.gz → 0.7.0__tar.gz
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-0.6.8/src/teareduce.egg-info → teareduce-0.7.0}/PKG-INFO +1 -1
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/__main__.py +74 -22
- teareduce-0.7.0/src/teareduce/cleanest/choose_index_with_three_buttons.py +126 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/cosmicraycleanerapp.py +192 -68
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/definitions.py +1 -1
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/interpolationeditor.py +73 -10
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/reviewcosmicray.py +182 -25
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/simulateccdexposure.py +3 -1
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/version.py +2 -2
- {teareduce-0.6.8 → teareduce-0.7.0/src/teareduce.egg-info}/PKG-INFO +1 -1
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce.egg-info/SOURCES.txt +1 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/LICENSE.txt +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/README.md +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/pyproject.toml +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/setup.cfg +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/__init__.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/avoid_astropy_warnings.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/__init__.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/askextension.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/centerchildparent.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/dilatemask.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/find_closest_true.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/gausskernel2d_elliptical.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/imagedisplay.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/interpolate.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_a.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_x.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_y.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/lacosmicpad.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/mergemasks.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/modalprogressbar.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/parametereditor.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cleanest/trackedbutton.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cookbook/__init__.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cookbook/get_cookbook_file.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/correct_pincushion_distortion.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/cosmicrays.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/ctext.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/draw_rectangle.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/elapsed_time.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/histogram1d.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/imshow.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/numsplines.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/peaks_spectrum.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/polfit.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/robust_std.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/sdistortion.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/sliceregion.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/statsummary.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/tests/__init__.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/tests/test_cleanest.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/tests/test_sliceregion.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/tests/test_version.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/wavecal.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/write_array_to_fits.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce/zscale.py +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce.egg-info/dependency_links.txt +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce.egg-info/entry_points.txt +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce.egg-info/requires.txt +0 -0
- {teareduce-0.6.8 → teareduce-0.7.0}/src/teareduce.egg-info/top_level.txt +0 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2025 Universidad Complutense de Madrid
|
|
2
|
+
# Copyright 2025-2026 Universidad Complutense de Madrid
|
|
3
3
|
#
|
|
4
4
|
# This file is part of teareduce
|
|
5
5
|
#
|
|
@@ -10,11 +10,10 @@
|
|
|
10
10
|
"""Interactive Cosmic Ray cleaning tool."""
|
|
11
11
|
|
|
12
12
|
import argparse
|
|
13
|
+
import glob
|
|
13
14
|
import tkinter as tk
|
|
14
15
|
from tkinter import filedialog
|
|
15
|
-
from tkinter import simpledialog
|
|
16
16
|
import os
|
|
17
|
-
from pathlib import Path
|
|
18
17
|
import platform
|
|
19
18
|
from rich import print
|
|
20
19
|
from rich_argparse import RichHelpFormatter
|
|
@@ -38,14 +37,7 @@ def main():
|
|
|
38
37
|
formatter_class=RichHelpFormatter,
|
|
39
38
|
)
|
|
40
39
|
parser.add_argument("input_fits", nargs="?", default=None, help="Path to the FITS file to be cleaned.")
|
|
41
|
-
parser.add_argument("--
|
|
42
|
-
parser.add_argument("--auxfile", type=str, default=None, help="Auxiliary FITS file")
|
|
43
|
-
parser.add_argument(
|
|
44
|
-
"--extension_auxfile",
|
|
45
|
-
type=str,
|
|
46
|
-
default="0",
|
|
47
|
-
help="FITS extension for auxiliary file (default: 0).",
|
|
48
|
-
)
|
|
40
|
+
parser.add_argument("--auxfile", type=str, default=None, help="Auxiliary FITS files (comma-separated if several).")
|
|
49
41
|
parser.add_argument(
|
|
50
42
|
"--fontfamily",
|
|
51
43
|
type=str,
|
|
@@ -114,17 +106,77 @@ def main():
|
|
|
114
106
|
use_auxfile = True
|
|
115
107
|
if use_auxfile:
|
|
116
108
|
print(f"Selected auxiliary FITS file: {args.auxfile}")
|
|
117
|
-
|
|
109
|
+
extension_auxfile = ask_extension_input_image(args.auxfile, imgshape=None)
|
|
110
|
+
args.auxfile = f"{args.auxfile}[{extension_auxfile}]"
|
|
111
|
+
else:
|
|
112
|
+
args.auxfile = None
|
|
118
113
|
root.destroy()
|
|
119
114
|
|
|
120
|
-
# Check that input
|
|
121
|
-
if
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
115
|
+
# Check that input file exists
|
|
116
|
+
if "[" in args.input_fits:
|
|
117
|
+
input_fits = args.input_fits[: args.input_fits.index("[")]
|
|
118
|
+
extension = args.input_fits[args.input_fits.index("[") + 1 : args.input_fits.index("]")]
|
|
119
|
+
else:
|
|
120
|
+
input_fits = args.input_fits
|
|
121
|
+
extension = "0"
|
|
122
|
+
if not os.path.isfile(input_fits):
|
|
123
|
+
print(f"Error: File '{input_fits}' does not exist.")
|
|
126
124
|
exit(1)
|
|
127
125
|
|
|
126
|
+
# Process auxiliary files if provided
|
|
127
|
+
auxfile_list = []
|
|
128
|
+
extension_auxfile_list = []
|
|
129
|
+
if args.auxfile is not None:
|
|
130
|
+
# Check several auxiliary files separated by commas
|
|
131
|
+
if "\n" in args.auxfile:
|
|
132
|
+
if "?" in args.auxfile or "*" in args.auxfile:
|
|
133
|
+
print("Error: Cannot combine newlines and wildcards in --auxfile specification.")
|
|
134
|
+
exit(1)
|
|
135
|
+
# Replace newlines by commas to allow:
|
|
136
|
+
# --auxfile="`ls file??.fits`" -> without extensions
|
|
137
|
+
# --auxfile="`for f in file??.fits; do echo "${f}[primary]"; done`" -> with extension name (the same for all files!)
|
|
138
|
+
args.auxfile = args.auxfile.replace("\n", ",")
|
|
139
|
+
elif "?" in args.auxfile or "*" in args.auxfile:
|
|
140
|
+
# Handle wildcards to allow:
|
|
141
|
+
# --auxfile="file??.fits" -> without extensions
|
|
142
|
+
# --auxfile="file??.fits[primary]" -> with extension name (the same for all files!)
|
|
143
|
+
if "," in args.auxfile:
|
|
144
|
+
print("Error: Cannot combine wildcards with commas in --auxfile specification.")
|
|
145
|
+
exit(1)
|
|
146
|
+
# Expand possible wildcards in the auxiliary file specification
|
|
147
|
+
if "[" in args.auxfile:
|
|
148
|
+
s = args.auxfile.strip()
|
|
149
|
+
ext = None
|
|
150
|
+
if s.endswith("]"):
|
|
151
|
+
ext = s[s.rfind("[") + 1 : s.rfind("]")]
|
|
152
|
+
s = s[: s.rfind("[")]
|
|
153
|
+
matched_files = sorted(glob.glob(s))
|
|
154
|
+
if len(matched_files) == 0:
|
|
155
|
+
print(f"Error: No files matched the pattern '{s}'.")
|
|
156
|
+
exit(1)
|
|
157
|
+
args.auxfile = ",".join([f"{fname}[{ext}]" if ext is not None else fname for fname in matched_files])
|
|
158
|
+
else:
|
|
159
|
+
matched_files = sorted(glob.glob(args.auxfile))
|
|
160
|
+
if len(matched_files) == 0:
|
|
161
|
+
print(f"Error: No files matched the pattern '{args.auxfile}'.")
|
|
162
|
+
exit(1)
|
|
163
|
+
args.auxfile = ",".join(matched_files)
|
|
164
|
+
# Now process the comma-separated auxiliary files
|
|
165
|
+
for item in args.auxfile.split(","):
|
|
166
|
+
# Extract possible [ext] from filename
|
|
167
|
+
if "[" in item:
|
|
168
|
+
# Separate filename and extension, removing blanks
|
|
169
|
+
fname = item[: item.index("[")].strip()
|
|
170
|
+
# Check that file exists
|
|
171
|
+
if not os.path.isfile(fname):
|
|
172
|
+
print(f"Error: File '{fname}' does not exist.")
|
|
173
|
+
exit(1)
|
|
174
|
+
auxfile_list.append(fname)
|
|
175
|
+
extension_auxfile_list.append(item[item.index("[") + 1 : item.index("]")])
|
|
176
|
+
else:
|
|
177
|
+
auxfile_list.append(item.strip())
|
|
178
|
+
extension_auxfile_list.append("0")
|
|
179
|
+
|
|
128
180
|
# Initialize Tkinter root
|
|
129
181
|
try:
|
|
130
182
|
root = tk.Tk()
|
|
@@ -149,10 +201,10 @@ def main():
|
|
|
149
201
|
# Create and run the application
|
|
150
202
|
CosmicRayCleanerApp(
|
|
151
203
|
root=root,
|
|
152
|
-
input_fits=
|
|
153
|
-
extension=
|
|
154
|
-
|
|
155
|
-
|
|
204
|
+
input_fits=input_fits,
|
|
205
|
+
extension=extension,
|
|
206
|
+
auxfile_list=auxfile_list,
|
|
207
|
+
extension_auxfile_list=extension_auxfile_list,
|
|
156
208
|
fontfamily=args.fontfamily,
|
|
157
209
|
fontsize=args.fontsize,
|
|
158
210
|
width=args.width,
|
|
@@ -0,0 +1,126 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2026 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
|
+
import tkinter as tk
|
|
11
|
+
from tkinter import simpledialog, ttk
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class ThreeButtonListDialog(simpledialog.Dialog):
|
|
15
|
+
"""
|
|
16
|
+
A modal dialog that shows a list of strings and returns:
|
|
17
|
+
- 1..N : the highlighted item index (1-based) when pressing OK (or double-click / Enter)
|
|
18
|
+
- 0 : when pressing Cancel or closing the window (Esc or window X)
|
|
19
|
+
- N+1 : when pressing New (N = len(items))
|
|
20
|
+
"""
|
|
21
|
+
|
|
22
|
+
def __init__(self, parent, title, prompt, items):
|
|
23
|
+
self.prompt = prompt
|
|
24
|
+
self.items = list(items)
|
|
25
|
+
self.result = None
|
|
26
|
+
self._force_new = False # used to bypass selection validation for "New"
|
|
27
|
+
super().__init__(parent, title)
|
|
28
|
+
|
|
29
|
+
# ---------- Core layout ----------
|
|
30
|
+
def body(self, master):
|
|
31
|
+
ttk.Label(master, text=self.prompt).grid(row=0, column=0, columnspan=1, sticky="w", padx=8, pady=(8, 4))
|
|
32
|
+
|
|
33
|
+
self.listbox = tk.Listbox(master, height=min(12, max(4, len(self.items))), activestyle="dotbox")
|
|
34
|
+
for s in self.items:
|
|
35
|
+
self.listbox.insert(tk.END, s)
|
|
36
|
+
self.listbox.grid(row=1, column=0, sticky="nsew", padx=(8, 0), pady=4)
|
|
37
|
+
|
|
38
|
+
scrollbar = ttk.Scrollbar(master, orient="vertical", command=self.listbox.yview)
|
|
39
|
+
self.listbox.configure(yscrollcommand=scrollbar.set)
|
|
40
|
+
scrollbar.grid(row=1, column=1, sticky="ns", pady=4, padx=(0, 8))
|
|
41
|
+
|
|
42
|
+
# Keyboard bindings
|
|
43
|
+
self.listbox.bind("<Double-Button-1>", lambda e: self.ok()) # OK on double-click
|
|
44
|
+
self.bind("<Return>", lambda e: self.ok()) # Enter = OK
|
|
45
|
+
self.bind("<Escape>", lambda e: self.cancel()) # Esc = Cancel (→ 0)
|
|
46
|
+
|
|
47
|
+
# Resize behavior
|
|
48
|
+
master.grid_columnconfigure(0, weight=1)
|
|
49
|
+
master.grid_rowconfigure(1, weight=1)
|
|
50
|
+
|
|
51
|
+
if self.items:
|
|
52
|
+
self.listbox.selection_set(0)
|
|
53
|
+
self.listbox.activate(0)
|
|
54
|
+
|
|
55
|
+
return self.listbox # initial focus on listbox
|
|
56
|
+
|
|
57
|
+
# ---------- Buttons (OK, Cancel, New) ----------
|
|
58
|
+
def buttonbox(self):
|
|
59
|
+
box = ttk.Frame(self)
|
|
60
|
+
|
|
61
|
+
self.ok_button = ttk.Button(box, text="OK", width=10, command=self.ok)
|
|
62
|
+
self.ok_button.grid(row=0, column=0, padx=5, pady=8)
|
|
63
|
+
|
|
64
|
+
self.cancel_button = ttk.Button(box, text="Cancel", width=10, command=self.cancel)
|
|
65
|
+
self.cancel_button.grid(row=0, column=1, padx=5, pady=8)
|
|
66
|
+
|
|
67
|
+
self.new_button = ttk.Button(box, text="New", width=10, command=self._on_new)
|
|
68
|
+
self.new_button.grid(row=0, column=2, padx=5, pady=8)
|
|
69
|
+
|
|
70
|
+
# Bindings for Return/Escape already set in body()
|
|
71
|
+
self.bind("<Return>", lambda e: self.ok())
|
|
72
|
+
self.bind("<Escape>", lambda e: self.cancel())
|
|
73
|
+
|
|
74
|
+
box.pack(side="bottom", fill="x")
|
|
75
|
+
|
|
76
|
+
# ---------- Dialog flow ----------
|
|
77
|
+
def validate(self):
|
|
78
|
+
"""
|
|
79
|
+
Called by OK to decide whether dialog can close.
|
|
80
|
+
- If 'New' was pressed, allow closing without selection.
|
|
81
|
+
- Otherwise, ensure there is a highlighted item.
|
|
82
|
+
"""
|
|
83
|
+
if self._force_new:
|
|
84
|
+
return True
|
|
85
|
+
cur = self.listbox.curselection()
|
|
86
|
+
if not cur:
|
|
87
|
+
self.bell()
|
|
88
|
+
return False
|
|
89
|
+
self._selected_index = cur[0]
|
|
90
|
+
return True
|
|
91
|
+
|
|
92
|
+
def apply(self):
|
|
93
|
+
"""
|
|
94
|
+
Called after validate(). Set the final result here.
|
|
95
|
+
"""
|
|
96
|
+
if self._force_new:
|
|
97
|
+
self.result = len(self.items) + 1 # N+1 (New)
|
|
98
|
+
else:
|
|
99
|
+
self.result = self._selected_index + 1 # 1..N (index of selection)
|
|
100
|
+
|
|
101
|
+
def _on_new(self):
|
|
102
|
+
"""
|
|
103
|
+
Trigger New: returns N+1.
|
|
104
|
+
This must bypass selection validation.
|
|
105
|
+
"""
|
|
106
|
+
self._force_new = True
|
|
107
|
+
self.ok()
|
|
108
|
+
|
|
109
|
+
# Important: Don't overwrite OK/New results when closing via Dialog.ok().
|
|
110
|
+
# Dialog.ok() calls validate() -> apply() -> cancel().
|
|
111
|
+
# Only set 0 in cancel() if result wasn't already set.
|
|
112
|
+
def cancel(self, event=None):
|
|
113
|
+
if self.result is None:
|
|
114
|
+
self.result = 0
|
|
115
|
+
super().cancel(event)
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
def choose_index_with_three_buttons(parent, title, prompt, items):
|
|
119
|
+
"""
|
|
120
|
+
Show the dialog and return:
|
|
121
|
+
- 1..N : highlighted item index (OK)
|
|
122
|
+
- 0 : Cancel/close
|
|
123
|
+
- N+1 : New
|
|
124
|
+
"""
|
|
125
|
+
dlg = ThreeButtonListDialog(parent, title, prompt, items)
|
|
126
|
+
return dlg.result
|