teareduce 0.6.9__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.
Files changed (60) hide show
  1. {teareduce-0.6.9/src/teareduce.egg-info → teareduce-0.7.0}/PKG-INFO +1 -1
  2. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/__main__.py +74 -22
  3. teareduce-0.7.0/src/teareduce/cleanest/choose_index_with_three_buttons.py +126 -0
  4. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/cosmicraycleanerapp.py +192 -68
  5. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/definitions.py +1 -1
  6. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/interpolationeditor.py +73 -10
  7. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/reviewcosmicray.py +182 -25
  8. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/version.py +2 -2
  9. {teareduce-0.6.9 → teareduce-0.7.0/src/teareduce.egg-info}/PKG-INFO +1 -1
  10. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce.egg-info/SOURCES.txt +1 -0
  11. {teareduce-0.6.9 → teareduce-0.7.0}/LICENSE.txt +0 -0
  12. {teareduce-0.6.9 → teareduce-0.7.0}/README.md +0 -0
  13. {teareduce-0.6.9 → teareduce-0.7.0}/pyproject.toml +0 -0
  14. {teareduce-0.6.9 → teareduce-0.7.0}/setup.cfg +0 -0
  15. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/__init__.py +0 -0
  16. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/avoid_astropy_warnings.py +0 -0
  17. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/__init__.py +0 -0
  18. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/askextension.py +0 -0
  19. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/centerchildparent.py +0 -0
  20. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/dilatemask.py +0 -0
  21. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/find_closest_true.py +0 -0
  22. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/gausskernel2d_elliptical.py +0 -0
  23. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/imagedisplay.py +0 -0
  24. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/interpolate.py +0 -0
  25. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_a.py +0 -0
  26. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_x.py +0 -0
  27. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/interpolation_y.py +0 -0
  28. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/lacosmicpad.py +0 -0
  29. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/mergemasks.py +0 -0
  30. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/modalprogressbar.py +0 -0
  31. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/parametereditor.py +0 -0
  32. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cleanest/trackedbutton.py +0 -0
  33. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cookbook/__init__.py +0 -0
  34. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cookbook/get_cookbook_file.py +0 -0
  35. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/correct_pincushion_distortion.py +0 -0
  36. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/cosmicrays.py +0 -0
  37. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/ctext.py +0 -0
  38. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/draw_rectangle.py +0 -0
  39. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/elapsed_time.py +0 -0
  40. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/histogram1d.py +0 -0
  41. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/imshow.py +0 -0
  42. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/numsplines.py +0 -0
  43. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/peaks_spectrum.py +0 -0
  44. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/polfit.py +0 -0
  45. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/robust_std.py +0 -0
  46. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/sdistortion.py +0 -0
  47. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/simulateccdexposure.py +0 -0
  48. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/sliceregion.py +0 -0
  49. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/statsummary.py +0 -0
  50. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/tests/__init__.py +0 -0
  51. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/tests/test_cleanest.py +0 -0
  52. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/tests/test_sliceregion.py +0 -0
  53. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/tests/test_version.py +0 -0
  54. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/wavecal.py +0 -0
  55. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/write_array_to_fits.py +0 -0
  56. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce/zscale.py +0 -0
  57. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce.egg-info/dependency_links.txt +0 -0
  58. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce.egg-info/entry_points.txt +0 -0
  59. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce.egg-info/requires.txt +0 -0
  60. {teareduce-0.6.9 → teareduce-0.7.0}/src/teareduce.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teareduce
3
- Version: 0.6.9
3
+ Version: 0.7.0
4
4
  Summary: Utilities for astronomical data reduction
5
5
  Author-email: Nicolás Cardiel <cardiel@ucm.es>
6
6
  License: GPL-3.0-or-later
@@ -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("--extension", type=str, default="0", help="FITS extension to use (default: 0).")
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
- args.extension_auxfile = ask_extension_input_image(args.auxfile, imgshape=None)
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 files, and the corresponding extensions, exist
121
- if not os.path.isfile(args.input_fits):
122
- print(f"Error: File '{args.input_fits}' does not exist.")
123
- exit(1)
124
- if args.auxfile is not None and not os.path.isfile(args.auxfile):
125
- print(f"Error: Auxiliary file '{args.auxfile}' does not exist.")
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=args.input_fits,
153
- extension=args.extension,
154
- auxfile=args.auxfile,
155
- extension_auxfile=args.extension_auxfile,
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