teareduce 0.5.5__py3-none-any.whl → 0.5.7__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7,4 +7,6 @@
7
7
  # License-Filename: LICENSE.txt
8
8
  #
9
9
 
10
- from .cleanest import cleanest
10
+ from .interpolate import interpolate
11
+ from .lacosmicpad import lacosmicpad
12
+ from .mergemasks import merge_peak_tail_masks
@@ -10,8 +10,12 @@
10
10
  """Interactive Cosmic Ray cleaning tool."""
11
11
 
12
12
  import argparse
13
+ from ast import arg
13
14
  import tkinter as tk
15
+ from tkinter import filedialog
16
+ from tkinter import simpledialog
14
17
  import os
18
+ from pathlib import Path
15
19
  import platform
16
20
  from rich import print
17
21
  from rich_argparse import RichHelpFormatter
@@ -21,6 +25,7 @@ from .definitions import DEFAULT_FONT_SIZE
21
25
  from .definitions import DEFAULT_TK_WINDOW_SIZE_X
22
26
  from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
23
27
  from .cosmicraycleanerapp import CosmicRayCleanerApp
28
+ from ..version import VERSION
24
29
 
25
30
  import matplotlib
26
31
 
@@ -32,13 +37,13 @@ def main():
32
37
  description="Interactive cosmic ray cleaner for FITS images.",
33
38
  formatter_class=RichHelpFormatter,
34
39
  )
35
- parser.add_argument("input_fits", help="Path to the FITS file to be cleaned.")
36
- parser.add_argument("--extension", type=int, default=0, help="FITS extension to use (default: 0).")
40
+ parser.add_argument("input_fits", nargs="?", default=None, help="Path to the FITS file to be cleaned.")
41
+ parser.add_argument("--extension", type=str, default="0", help="FITS extension to use (default: 0).")
37
42
  parser.add_argument("--auxfile", type=str, default=None, help="Auxiliary FITS file")
38
43
  parser.add_argument(
39
44
  "--extension_auxfile",
40
- type=int,
41
- default=0,
45
+ type=str,
46
+ default="0",
42
47
  help="FITS extension for auxiliary file (default: 0).",
43
48
  )
44
49
  parser.add_argument(
@@ -65,14 +70,67 @@ def main():
65
70
  default=DEFAULT_TK_WINDOW_SIZE_Y,
66
71
  help=f"Height of the GUI window in pixels (default: {DEFAULT_TK_WINDOW_SIZE_Y}).",
67
72
  )
73
+ parser.add_argument("--version", action="version", version=f"%(prog)s {VERSION}")
74
+ parser.add_argument("--verbose", "-v", action="store_true", help="Enable verbose output.")
68
75
  args = parser.parse_args()
69
76
 
77
+ # Welcome message
78
+ print("[bold green]Cosmic Ray Cleaner[/bold green]")
79
+ print("Interactive tool to clean cosmic rays from FITS images.")
80
+ print("teareduce version: " + VERSION)
81
+ print(f"https://nicocardiel.github.io/teareduce-cookbook/docs/cleanest/cleanest.html\n")
82
+
83
+ # If input_file is not provided, ask for it using a file dialog
84
+ if args.input_fits is None:
85
+ root = tk.Tk()
86
+ root.withdraw() # Hide the root window
87
+ args.input_fits = filedialog.askopenfilename(
88
+ title="Select FITS file to be cleaned",
89
+ filetypes=[("FITS files", "*.fits *.fit *.fts"), ("All files", "*.*")],
90
+ )
91
+ if not args.input_fits:
92
+ print("No input FITS file selected. Exiting.")
93
+ exit(1)
94
+ print(f"Selected input FITS file: {args.input_fits}")
95
+ args.extension = simpledialog.askstring(
96
+ "Select Extension",
97
+ f"\nEnter extension number or name for file:\n{Path(args.input_fits).name}",
98
+ initialvalue=args.extension,
99
+ )
100
+ # Ask for auxiliary file if not provided
101
+ if args.auxfile is None:
102
+ use_auxfile = tk.messagebox.askyesno(
103
+ "Auxiliary File",
104
+ "Do you want to use an auxiliary FITS file?",
105
+ default=tk.messagebox.NO,
106
+ )
107
+ if use_auxfile:
108
+ args.auxfile = filedialog.askopenfilename(
109
+ title="Select Auxiliary FITS file",
110
+ filetypes=[("FITS files", "*.fits *.fit *.fts"), ("All files", "*.*")],
111
+ initialfile=args.auxfile,
112
+ )
113
+ if not args.auxfile:
114
+ print("No auxiliary FITS file selected. Exiting.")
115
+ exit(1)
116
+ else:
117
+ use_auxfile = True
118
+ if use_auxfile:
119
+ print(f"Selected auxiliary FITS file: {args.auxfile}")
120
+ args.extension_auxfile = simpledialog.askstring(
121
+ "Select Extension for Auxiliary File",
122
+ f"\nEnter extension number or name for auxiliary file:\n{Path(args.auxfile).name}",
123
+ initialvalue=args.extension_auxfile,
124
+ )
125
+ root.destroy()
126
+
127
+ # Check that input files, and the corresponding extensions, exist
70
128
  if not os.path.isfile(args.input_fits):
71
129
  print(f"Error: File '{args.input_fits}' does not exist.")
72
- return
130
+ exit(1)
73
131
  if args.auxfile is not None and not os.path.isfile(args.auxfile):
74
132
  print(f"Error: Auxiliary file '{args.auxfile}' does not exist.")
75
- return
133
+ exit(1)
76
134
 
77
135
  # Initialize Tkinter root
78
136
  root = tk.Tk()
@@ -100,6 +158,7 @@ def main():
100
158
  fontsize=args.fontsize,
101
159
  width=args.width,
102
160
  height=args.height,
161
+ verbose=args.verbose,
103
162
  )
104
163
 
105
164
  # Execute
@@ -13,16 +13,17 @@ import tkinter as tk
13
13
  from tkinter import filedialog
14
14
  from tkinter import font as tkfont
15
15
  from tkinter import messagebox
16
+ from tkinter import simpledialog
16
17
  import sys
17
18
 
18
19
  from astropy.io import fits
19
- from ccdproc import cosmicray_lacosmic
20
20
  import matplotlib.pyplot as plt
21
21
  from matplotlib.backend_bases import key_press_handler
22
22
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
23
23
  from scipy import ndimage
24
24
  import numpy as np
25
25
  import os
26
+ from pathlib import Path
26
27
  from rich import print
27
28
 
28
29
  from .centerchildparent import center_on_parent
@@ -41,6 +42,8 @@ from .interpolation_x import interpolation_x
41
42
  from .interpolation_y import interpolation_y
42
43
  from .interpolationeditor import InterpolationEditor
43
44
  from .imagedisplay import ImageDisplay
45
+ from .lacosmicpad import lacosmicpad
46
+ from .mergemasks import merge_peak_tail_masks
44
47
  from .parametereditor import ParameterEditor
45
48
  from .reviewcosmicray import ReviewCosmicRay
46
49
  from .modalprogressbar import ModalProgressBar
@@ -61,13 +64,14 @@ class CosmicRayCleanerApp(ImageDisplay):
61
64
  self,
62
65
  root,
63
66
  input_fits,
64
- extension=0,
67
+ extension="0",
65
68
  auxfile=None,
66
- extension_auxfile=0,
69
+ extension_auxfile="0",
67
70
  fontfamily=DEFAULT_FONT_FAMILY,
68
71
  fontsize=DEFAULT_FONT_SIZE,
69
72
  width=DEFAULT_TK_WINDOW_SIZE_X,
70
73
  height=DEFAULT_TK_WINDOW_SIZE_Y,
74
+ verbose=False,
71
75
  ):
72
76
  """
73
77
  Initialize the application.
@@ -78,19 +82,29 @@ class CosmicRayCleanerApp(ImageDisplay):
78
82
  The main Tkinter window.
79
83
  input_fits : str
80
84
  Path to the FITS file to be cleaned.
81
- extension : int, optional
82
- FITS extension to use (default is 0).
85
+ extension : str, optional
86
+ FITS extension to use (default is "0").
83
87
  auxfile : str, optional
84
88
  Path to an auxiliary FITS file (default is None).
85
- extension_auxfile : int, optional
86
- FITS extension for auxiliary file (default is 0).
89
+ extension_auxfile : str, optional
90
+ FITS extension for auxiliary file (default is "0").
87
91
  fontfamily : str, optional
88
92
  Font family for the GUI (default is "Helvetica").
89
93
  fontsize : int, optional
90
94
  Font size for the GUI (default is 14).
95
+ width : int, optional
96
+ Width of the GUI window in pixels (default is 800).
97
+ height : int, optional
98
+ Height of the GUI window in pixels (default is 600).
99
+ verbose : bool, optional
100
+ Enable verbose output (default is False).
91
101
 
92
102
  Methods
93
103
  -------
104
+ process_detected_cr(dilation)
105
+ Process the detected cosmic ray mask.
106
+ load_detected_cr_from_file()
107
+ Load detected cosmic ray mask from a FITS file.
94
108
  load_fits_file()
95
109
  Load the FITS file and auxiliary file (if provided).
96
110
  save_fits_file()
@@ -124,6 +138,12 @@ class CosmicRayCleanerApp(ImageDisplay):
124
138
  Font size for the GUI.
125
139
  default_font : tkfont.Font
126
140
  The default font used in the GUI.
141
+ width : int
142
+ Width of the GUI window in pixels.
143
+ height : int
144
+ Height of the GUI window in pixels.
145
+ verbose : bool
146
+ Enable verbose output.
127
147
  lacosmic_params : dict
128
148
  Dictionary of L.A.Cosmic parameters.
129
149
  input_fits : str
@@ -171,6 +191,7 @@ class CosmicRayCleanerApp(ImageDisplay):
171
191
  # self.root.geometry("800x800+50+0") # This does not work in Fedora
172
192
  self.width = width
173
193
  self.height = height
194
+ self.verbose = verbose
174
195
  self.root.minsize(self.width, self.height)
175
196
  self.root.update_idletasks()
176
197
  self.root.title("Cosmic Ray Cleaner")
@@ -181,6 +202,8 @@ class CosmicRayCleanerApp(ImageDisplay):
181
202
  family=fontfamily, size=fontsize, weight="normal", slant="roman", underline=0, overstrike=0
182
203
  )
183
204
  self.lacosmic_params = lacosmic_default_dict.copy()
205
+ self.lacosmic_params["run1_verbose"]["value"] = self.verbose
206
+ self.lacosmic_params["run2_verbose"]["value"] = self.verbose
184
207
  self.input_fits = input_fits
185
208
  self.extension = extension
186
209
  self.data = None
@@ -202,6 +225,93 @@ class CosmicRayCleanerApp(ImageDisplay):
202
225
  self.num_features = 0
203
226
  self.working_in_review_window = False
204
227
 
228
+ def process_detected_cr(self, dilation):
229
+ """Process the detected cosmic ray mask.
230
+
231
+ Parameters
232
+ ----------
233
+ dilation : int
234
+ Number of pixels to dilate the cosmic ray mask.
235
+ """
236
+ # Process the mask: dilation and labeling
237
+ if np.any(self.mask_crfound):
238
+ num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
239
+ if dilation > 0:
240
+ # Dilate the mask by the specified number of pixels
241
+ self.mask_crfound = dilatemask(
242
+ mask=self.mask_crfound, iterations=dilation, connectivity=1
243
+ )
244
+ num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
245
+ sdum = str(num_cr_pixels_after_dilation)
246
+ else:
247
+ sdum = str(num_cr_pixels_before_dilation)
248
+ print(
249
+ "Number of cosmic ray pixels detected..........: "
250
+ f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
251
+ )
252
+ if dilation > 0:
253
+ print(
254
+ f"Number of cosmic ray pixels after dilation....: "
255
+ f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
256
+ )
257
+ # Label connected components in the mask; note that by default,
258
+ # structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
259
+ # diagonal connections too, so we define a 3x3 square.
260
+ structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
261
+ self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
262
+ print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
263
+ self.replace_detected_cr_button.config(state=tk.NORMAL)
264
+ self.examine_detected_cr_button.config(state=tk.NORMAL)
265
+ self.update_cr_overlay()
266
+ self.use_cursor = True
267
+ self.use_cursor_button.config(text="[c]ursor: ON ")
268
+ else:
269
+ print("No cosmic ray pixels detected!")
270
+ self.cr_labels = None
271
+ self.num_features = 0
272
+ self.replace_detected_cr_button.config(state=tk.DISABLED)
273
+ self.examine_detected_cr_button.config(state=tk.DISABLED)
274
+
275
+ def load_detected_cr_from_file(self):
276
+ """Load detected cosmic ray mask from a FITS file."""
277
+ crmask_file = filedialog.askopenfilename(
278
+ initialdir=os.getcwd(),
279
+ title="Select FITS file with cosmic ray mask",
280
+ filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
281
+ )
282
+ if crmask_file:
283
+ print(f"Selected input FITS file: {crmask_file}")
284
+ extension = simpledialog.askstring(
285
+ "Select Extension",
286
+ f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
287
+ initialvalue=None,
288
+ )
289
+ try:
290
+ extension = int(extension)
291
+ except ValueError:
292
+ pass # Keep as string
293
+ dilation = simpledialog.askinteger(
294
+ "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
295
+ )
296
+ try:
297
+ with fits.open(crmask_file, mode="readonly") as hdul:
298
+ if isinstance(extension, int):
299
+ if extension < 0 or extension >= len(hdul):
300
+ raise IndexError(f"Extension index {extension} out of range.")
301
+ else:
302
+ if extension not in hdul:
303
+ raise KeyError(f"Extension name '{extension}' not found.")
304
+ mask_crfound_loaded = hdul[extension].data.astype(bool)
305
+ if mask_crfound_loaded.shape != self.data.shape:
306
+ print(f"data shape...: {self.data.shape}")
307
+ print(f"mask shape...: {mask_crfound_loaded.shape}")
308
+ raise ValueError("Cosmic ray mask has different shape.")
309
+ self.mask_crfound = mask_crfound_loaded
310
+ print(f"Loaded cosmic ray mask from {crmask_file}")
311
+ self.process_detected_cr(dilation=dilation)
312
+ except Exception as e:
313
+ print(f"Error loading cosmic ray mask: {e}")
314
+
205
315
  def load_fits_file(self):
206
316
  """Load the FITS file and auxiliary file (if provided).
207
317
 
@@ -216,8 +326,22 @@ class CosmicRayCleanerApp(ImageDisplay):
216
326
  provided, it also loads the auxiliary data from the specified extension.
217
327
  The loaded data is stored in `self.data` and `self.auxdata` attributes.
218
328
  """
329
+ # check if extension is compatible with an integer
330
+ try:
331
+ extnum = int(self.extension)
332
+ self.extension = extnum
333
+ except ValueError:
334
+ # Keep as string (delaying checking until opening the file)
335
+ self.extension = self.extension.upper() # Convert to uppercase
219
336
  try:
220
337
  with fits.open(self.input_fits, mode="readonly") as hdul:
338
+ if isinstance(self.extension, int):
339
+ if self.extension < 0 or self.extension >= len(hdul):
340
+ raise IndexError(f"Extension index {self.extension} out of range.")
341
+ else:
342
+ if self.extension not in hdul:
343
+ raise KeyError(f"Extension name '{self.extension}' not found.")
344
+ print(f"Reading file [bold green]{self.input_fits}[/bold green], extension {self.extension}")
221
345
  self.data = hdul[self.extension].data
222
346
  if "CRMASK" in hdul:
223
347
  self.mask_fixed = hdul["CRMASK"].data.astype(bool)
@@ -225,13 +349,28 @@ class CosmicRayCleanerApp(ImageDisplay):
225
349
  self.mask_fixed = np.zeros(self.data.shape, dtype=bool)
226
350
  except Exception as e:
227
351
  print(f"Error loading FITS file: {e}")
352
+ sys.exit(1)
228
353
  self.mask_crfound = np.zeros(self.data.shape, dtype=bool)
229
354
  naxis2, naxis1 = self.data.shape
230
355
  self.region = SliceRegion2D(f"[1:{naxis1}, 1:{naxis2}]", mode="fits").python
231
356
  # Read auxiliary file if provided
232
357
  if self.auxfile is not None:
358
+ # check if extension_auxfile is compatible with an integer
359
+ try:
360
+ extnum_aux = int(self.extension_auxfile)
361
+ self.extension_auxfile = extnum_aux
362
+ except ValueError:
363
+ # Keep as string (delaying checking until opening the file)
364
+ self.extension_auxfile = self.extension_auxfile.upper() # Convert to uppercase
233
365
  try:
234
366
  with fits.open(self.auxfile, mode="readonly") as hdul_aux:
367
+ if isinstance(self.extension_auxfile, int):
368
+ if self.extension_auxfile < 0 or self.extension_auxfile >= len(hdul_aux):
369
+ raise IndexError(f"Extension index {self.extension_auxfile} out of range.")
370
+ else:
371
+ if self.extension_auxfile not in hdul_aux:
372
+ raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
373
+ print(f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}")
235
374
  self.auxdata = hdul_aux[self.extension_auxfile].data
236
375
  if self.auxdata.shape != self.data.shape:
237
376
  print(f"data shape...: {self.data.shape}")
@@ -308,20 +447,15 @@ class CosmicRayCleanerApp(ImageDisplay):
308
447
  self.button_frame1.pack(pady=5)
309
448
  self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
310
449
  self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
311
- if self.overplot_cr_pixels:
312
- self.overplot_cr_button = tk.Button(
313
- self.button_frame1, text="CR overlay: On", command=self.toggle_cr_overlay
314
- )
315
- else:
316
- self.overplot_cr_button = tk.Button(
317
- self.button_frame1, text="CR overlay: Off", command=self.toggle_cr_overlay
318
- )
319
- self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
320
- self.apply_lacosmic_button = tk.Button(
450
+ self.load_detected_cr_button = tk.Button(
451
+ self.button_frame1, text="Load detected CRs", command=self.load_detected_cr_from_file
452
+ )
453
+ self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
454
+ self.replace_detected_cr_button = tk.Button(
321
455
  self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
322
456
  )
323
- self.apply_lacosmic_button.pack(side=tk.LEFT, padx=5)
324
- self.apply_lacosmic_button.config(state=tk.DISABLED) # Initially disabled
457
+ self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
458
+ self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
325
459
  self.examine_detected_cr_button = tk.Button(
326
460
  self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
327
461
  )
@@ -331,6 +465,17 @@ class CosmicRayCleanerApp(ImageDisplay):
331
465
  # Row 2 of buttons
332
466
  self.button_frame2 = tk.Frame(self.root)
333
467
  self.button_frame2.pack(pady=5)
468
+ self.toggle_auxdata_button = tk.Button(self.button_frame2, text="[t]oggle data", command=self.toggle_auxdata)
469
+ self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
470
+ if self.auxdata is None:
471
+ self.toggle_auxdata_button.config(state=tk.DISABLED)
472
+ else:
473
+ self.toggle_auxdata_button.config(state=tk.NORMAL)
474
+ self.image_aspect = "equal"
475
+ self.toggle_aspect_button = tk.Button(
476
+ self.button_frame2, text=f"[a]spect: {self.image_aspect}", command=self.toggle_aspect
477
+ )
478
+ self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
334
479
  self.save_button = tk.Button(self.button_frame2, text="Save cleaned FITS", command=self.save_fits_file)
335
480
  self.save_button.pack(side=tk.LEFT, padx=5)
336
481
  self.save_button.config(state=tk.DISABLED) # Initially disabled
@@ -340,6 +485,9 @@ class CosmicRayCleanerApp(ImageDisplay):
340
485
  # Row 3 of buttons
341
486
  self.button_frame3 = tk.Frame(self.root)
342
487
  self.button_frame3.pack(pady=5)
488
+ self.use_cursor = False
489
+ self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
490
+ self.use_cursor_button.pack(side=tk.LEFT, padx=5)
343
491
  vmin, vmax = zscale(self.data)
344
492
  self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
345
493
  self.vmin_button.pack(side=tk.LEFT, padx=5)
@@ -349,6 +497,19 @@ class CosmicRayCleanerApp(ImageDisplay):
349
497
  self.set_minmax_button.pack(side=tk.LEFT, padx=5)
350
498
  self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
351
499
  self.set_zscale_button.pack(side=tk.LEFT, padx=5)
500
+ if self.overplot_cr_pixels:
501
+ self.overplot_cr_button = tk.Button(
502
+ self.button_frame3,
503
+ text="CR overlay: ON ",
504
+ command=self.toggle_cr_overlay,
505
+ )
506
+ else:
507
+ self.overplot_cr_button = tk.Button(
508
+ self.button_frame3,
509
+ text="CR overlay: OFF",
510
+ command=self.toggle_cr_overlay,
511
+ )
512
+ self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
352
513
 
353
514
  # Figure
354
515
  self.plot_frame = tk.Frame(self.root)
@@ -378,19 +539,63 @@ class CosmicRayCleanerApp(ImageDisplay):
378
539
  xlabel = "X pixel (from 1 to NAXIS1)"
379
540
  ylabel = "Y pixel (from 1 to NAXIS2)"
380
541
  extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
542
+ self.image_aspect = "equal"
543
+ self.displaying_auxdata = False
381
544
  self.image, _, _ = imshow(
382
- self.fig,
383
- self.ax,
384
- self.data,
545
+ fig=self.fig,
546
+ ax=self.ax,
547
+ data=self.data,
385
548
  vmin=vmin,
386
549
  vmax=vmax,
387
- title=os.path.basename(self.input_fits),
550
+ title=f"data: {os.path.basename(self.input_fits)}",
388
551
  xlabel=xlabel,
389
552
  ylabel=ylabel,
390
553
  extent=extent,
554
+ aspect=self.image_aspect,
391
555
  )
392
556
  self.fig.tight_layout()
393
557
 
558
+ def set_cursor_onoff(self):
559
+ """Toggle cursor selection mode on or off."""
560
+ if not self.use_cursor:
561
+ self.use_cursor = True
562
+ self.use_cursor_button.config(text="[c]ursor: ON ")
563
+ else:
564
+ self.use_cursor = False
565
+ self.use_cursor_button.config(text="[c]ursor: OFF")
566
+
567
+ def toggle_auxdata(self):
568
+ """Toggle between main data and auxiliary data for display."""
569
+ if self.displaying_auxdata:
570
+ # Switch to main data
571
+ vmin = self.get_vmin()
572
+ vmax = self.get_vmax()
573
+ self.image.set_data(self.data)
574
+ self.image.set_clim(vmin=vmin, vmax=vmax)
575
+ self.displaying_auxdata = False
576
+ self.ax.set_title(f"data: {os.path.basename(self.input_fits)}")
577
+ else:
578
+ # Switch to auxiliary data
579
+ vmin = self.get_vmin()
580
+ vmax = self.get_vmax()
581
+ self.image.set_data(self.auxdata)
582
+ self.image.set_clim(vmin=vmin, vmax=vmax)
583
+ self.displaying_auxdata = True
584
+ self.ax.set_title(f"auxdata: {os.path.basename(self.auxfile)}")
585
+ self.canvas.draw_idle()
586
+
587
+ def toggle_aspect(self):
588
+ """Toggle the aspect ratio of the image display."""
589
+ if self.image_aspect == "equal":
590
+ self.image_aspect = "auto"
591
+ else:
592
+ self.image_aspect = "equal"
593
+ print(f"Setting image aspect to: {self.image_aspect}")
594
+ self.toggle_aspect_button.config(text=f"[a]spect: {self.image_aspect}")
595
+ self.ax.set_aspect(self.image_aspect)
596
+ self.fig.tight_layout()
597
+ self.canvas.draw_idle()
598
+
394
599
  def run_lacosmic(self):
395
600
  """Run L.A.Cosmic to detect cosmic rays."""
396
601
  self.run_lacosmic_button.config(state=tk.DISABLED)
@@ -427,17 +632,18 @@ class CosmicRayCleanerApp(ImageDisplay):
427
632
  usefulmask[usefulregion] = 1.0
428
633
  # Update parameter dictionary with new values
429
634
  self.lacosmic_params = updated_params
430
- print("Parameters updated:")
431
- for key, info in self.lacosmic_params.items():
432
- print(f" {key}: {info['value']}")
635
+ if self.verbose:
636
+ print("Parameters updated:")
637
+ for key, info in self.lacosmic_params.items():
638
+ print(f" {key}: {info['value']}")
433
639
  if self.lacosmic_params["nruns"]["value"] not in [1, 2]:
434
640
  raise ValueError("nruns must be 1 or 2")
435
641
  # Execute L.A.Cosmic with updated parameters
436
642
  print("[bold green]Executing L.A.Cosmic (run 1)...[/bold green]")
437
643
  borderpadd = updated_params["borderpadd"]["value"]
438
- data_reflection_padded = np.pad(self.data, pad_width=borderpadd, mode="reflect")
439
- cleandata_lacosmic, mask_crfound = cosmicray_lacosmic(
440
- ccd=data_reflection_padded,
644
+ cleandata_lacosmic, mask_crfound = lacosmicpad(
645
+ pad_width=borderpadd,
646
+ ccd=self.data,
441
647
  gain=self.lacosmic_params["run1_gain"]["value"],
442
648
  readnoise=self.lacosmic_params["run1_readnoise"]["value"],
443
649
  sigclip=self.lacosmic_params["run1_sigclip"]["value"],
@@ -446,16 +652,15 @@ class CosmicRayCleanerApp(ImageDisplay):
446
652
  niter=self.lacosmic_params["run1_niter"]["value"],
447
653
  verbose=self.lacosmic_params["run1_verbose"]["value"],
448
654
  )
449
- cleandata_lacosmic = cleandata_lacosmic[borderpadd:-borderpadd, borderpadd:-borderpadd]
450
- mask_crfound = mask_crfound[borderpadd:-borderpadd, borderpadd:-borderpadd]
451
655
  # Apply usefulmask to consider only selected region
452
656
  cleandata_lacosmic *= usefulmask
453
657
  mask_crfound = mask_crfound & (usefulmask.astype(bool))
454
658
  # Second execution if nruns == 2
455
659
  if self.lacosmic_params["nruns"]["value"] == 2:
456
660
  print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
457
- cleandata_lacosmic2, mask_crfound2 = cosmicray_lacosmic(
458
- ccd=data_reflection_padded,
661
+ cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
662
+ pad_width=borderpadd,
663
+ ccd=self.data,
459
664
  gain=self.lacosmic_params["run2_gain"]["value"],
460
665
  readnoise=self.lacosmic_params["run2_readnoise"]["value"],
461
666
  sigclip=self.lacosmic_params["run2_sigclip"]["value"],
@@ -464,8 +669,6 @@ class CosmicRayCleanerApp(ImageDisplay):
464
669
  niter=self.lacosmic_params["run2_niter"]["value"],
465
670
  verbose=self.lacosmic_params["run2_verbose"]["value"],
466
671
  )
467
- cleandata_lacosmic2 = cleandata_lacosmic2[borderpadd:-borderpadd, borderpadd:-borderpadd]
468
- mask_crfound2 = mask_crfound2[borderpadd:-borderpadd, borderpadd:-borderpadd]
469
672
  # Apply usefulmask to consider only selected region
470
673
  cleandata_lacosmic2 *= usefulmask
471
674
  mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
@@ -473,19 +676,7 @@ class CosmicRayCleanerApp(ImageDisplay):
473
676
  if np.any(mask_crfound):
474
677
  print(f"Number of cosmic ray pixels (run1).......: {np.sum(mask_crfound)}")
475
678
  print(f"Number of cosmic ray pixels (run2).......: {np.sum(mask_crfound2)}")
476
- # find features in second run
477
- structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
478
- cr_labels2, num_features2 = ndimage.label(mask_crfound2, structure=structure)
479
- # generate mask of ones at CR pixels found in first run
480
- mask_peaks = np.zeros(mask_crfound.shape, dtype=float)
481
- mask_peaks[mask_crfound] = 1.0
482
- # preserve only those CR pixels in second run that are in the first run
483
- cr_labels2_preserved = mask_peaks * cr_labels2
484
- # generate new mask with preserved CR pixels from second run
485
- mask_crfound = np.zeros_like(mask_crfound, dtype=bool)
486
- for icr in np.unique(cr_labels2_preserved):
487
- if icr > 0:
488
- mask_crfound[cr_labels2 == icr] = True
679
+ mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2)
489
680
  print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
490
681
  # Use the cleandata from the second run
491
682
  cleandata_lacosmic = cleandata_lacosmic2
@@ -495,42 +686,7 @@ class CosmicRayCleanerApp(ImageDisplay):
495
686
  self.mask_crfound = np.zeros_like(self.data, dtype=bool)
496
687
  self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
497
688
  # Process the mask: dilation and labeling
498
- if np.any(self.mask_crfound):
499
- num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
500
- dilation = self.lacosmic_params["dilation"]["value"]
501
- if dilation > 0:
502
- # Dilate the mask by the specified number of pixels
503
- self.mask_crfound = dilatemask(
504
- mask=self.mask_crfound, iterations=self.lacosmic_params["dilation"]["value"], connectivity=1
505
- )
506
- num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
507
- sdum = str(num_cr_pixels_after_dilation)
508
- else:
509
- sdum = str(num_cr_pixels_before_dilation)
510
- print(
511
- "Number of cosmic ray pixels detected by L.A.Cosmic: "
512
- f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
513
- )
514
- if dilation > 0:
515
- print(
516
- f"Number of cosmic ray pixels after dilation........: "
517
- f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
518
- )
519
- # Label connected components in the mask; note that by default,
520
- # structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
521
- # diagonal connections too, so we define a 3x3 square.
522
- structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
523
- self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
524
- print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
525
- self.apply_lacosmic_button.config(state=tk.NORMAL)
526
- self.examine_detected_cr_button.config(state=tk.NORMAL)
527
- self.update_cr_overlay()
528
- else:
529
- print("No cosmic ray pixels detected by L.A.Cosmic.")
530
- self.cr_labels = None
531
- self.num_features = 0
532
- self.apply_lacosmic_button.config(state=tk.DISABLED)
533
- self.examine_detected_cr_button.config(state=tk.DISABLED)
689
+ self.process_detected_cr(dilation=self.lacosmic_params["dilation"]["value"])
534
690
  else:
535
691
  print("Parameter editing cancelled. L.A.Cosmic detection skipped!")
536
692
  self.run_lacosmic_button.config(state=tk.NORMAL)
@@ -539,9 +695,9 @@ class CosmicRayCleanerApp(ImageDisplay):
539
695
  """Toggle the overlay of cosmic ray pixels on the image."""
540
696
  self.overplot_cr_pixels = not self.overplot_cr_pixels
541
697
  if self.overplot_cr_pixels:
542
- self.overplot_cr_button.config(text="CR overlay: On")
698
+ self.overplot_cr_button.config(text="CR overlay: ON ")
543
699
  else:
544
- self.overplot_cr_button.config(text="CR overlay: Off")
700
+ self.overplot_cr_button.config(text="CR overlay: OFF")
545
701
  self.update_cr_overlay()
546
702
 
547
703
  def update_cr_overlay(self):
@@ -699,9 +855,13 @@ class CosmicRayCleanerApp(ImageDisplay):
699
855
  # recalculate labels and number of features
700
856
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
701
857
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
702
- sdum = str(np.sum(self.mask_crfound))
858
+ num_cr_remaining = np.sum(self.mask_crfound)
859
+ sdum = str(num_cr_remaining)
703
860
  print(f"Remaining number of cosmic ray pixels...................: {sdum}")
704
861
  print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
862
+ if num_cr_remaining == 0:
863
+ self.use_cursor = False
864
+ self.use_cursor_button.config(text="[c]ursor: OFF")
705
865
  # redraw image to show the changes
706
866
  self.image.set_data(self.data)
707
867
  self.canvas.draw_idle()
@@ -709,7 +869,7 @@ class CosmicRayCleanerApp(ImageDisplay):
709
869
  self.save_button.config(state=tk.NORMAL)
710
870
  if self.num_features == 0:
711
871
  self.examine_detected_cr_button.config(state=tk.DISABLED)
712
- self.apply_lacosmic_button.config(state=tk.DISABLED)
872
+ self.replace_detected_cr_button.config(state=tk.DISABLED)
713
873
  self.update_cr_overlay()
714
874
 
715
875
  def examine_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
@@ -765,9 +925,13 @@ class CosmicRayCleanerApp(ImageDisplay):
765
925
  # recalculate labels and number of features
766
926
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
767
927
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
768
- sdum = str(np.sum(self.mask_crfound))
928
+ num_remaining = np.sum(self.mask_crfound)
929
+ sdum = str(num_remaining)
769
930
  print(f"Remaining number of cosmic ray pixels...................: {sdum}")
770
931
  print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
932
+ if num_remaining == 0:
933
+ self.use_cursor = False
934
+ self.use_cursor_button.config(text="[c]ursor: OFF")
771
935
  # redraw image to show the changes
772
936
  self.image.set_data(self.data)
773
937
  self.canvas.draw_idle()
@@ -775,7 +939,7 @@ class CosmicRayCleanerApp(ImageDisplay):
775
939
  self.save_button.config(state=tk.NORMAL)
776
940
  if self.num_features == 0:
777
941
  self.examine_detected_cr_button.config(state=tk.DISABLED)
778
- self.apply_lacosmic_button.config(state=tk.DISABLED)
942
+ self.replace_detected_cr_button.config(state=tk.DISABLED)
779
943
  self.update_cr_overlay()
780
944
 
781
945
  def stop_app(self):
@@ -792,14 +956,39 @@ class CosmicRayCleanerApp(ImageDisplay):
792
956
 
793
957
  def on_key(self, event):
794
958
  """Handle key press events."""
795
- if event.key == "q":
796
- pass # Ignore the "q" key to prevent closing the window
959
+ if event.key == "c":
960
+ self.set_cursor_onoff()
961
+ elif event.key == "a":
962
+ self.toggle_aspect()
963
+ elif event.key == "t" and self.auxdata is not None:
964
+ self.toggle_auxdata()
797
965
  elif event.key == ",":
798
966
  self.set_minmax()
799
967
  elif event.key == "/":
800
968
  self.set_zscale()
801
- else:
802
- print(f"Key pressed: {event.key}")
969
+ elif event.key == "o":
970
+ self.toolbar.zoom()
971
+ elif event.key == "h":
972
+ self.toolbar.home()
973
+ elif event.key == "p":
974
+ self.toolbar.pan()
975
+ elif event.key == "s":
976
+ self.toolbar.save_figure()
977
+ elif event.key == "?":
978
+ # Display list of keyboard shortcuts
979
+ print("[bold blue]Keyboard Shortcuts:[/bold blue]")
980
+ print("[red] c [/red]: Toggle cursor selection mode on/off")
981
+ print("[red] t [/red]: Toggle between main data and auxiliary data")
982
+ print("[red] a [/red]: Toggle image aspect ratio equal/auto")
983
+ print("[red] , [/red]: Set vmin and vmax to minmax")
984
+ print("[red] / [/red]: Set vmin and vmax using zscale")
985
+ print("[red] h [/red]: Go to home view \\[toolbar]")
986
+ print("[red] o [/red]: Activate zoom mode \\[toolbar]")
987
+ print("[red] p [/red]: Activate pan mode \\[toolbar]")
988
+ print("[red] s [/red]: Save the current figure \\[toolbar]")
989
+ print("[red] q [/red]: (ignored) prevent closing the window")
990
+ elif event.key == "q":
991
+ pass # Ignore the "q" key to prevent closing the window
803
992
 
804
993
  def on_click(self, event):
805
994
  """Handle mouse click events on the image."""
@@ -814,6 +1003,10 @@ class CosmicRayCleanerApp(ImageDisplay):
814
1003
  print(f"Toolbar mode '{toolbar.mode}' active; click ignored.")
815
1004
  return
816
1005
 
1006
+ # proceed only if cursor selection mode is on
1007
+ if not self.use_cursor:
1008
+ return
1009
+
817
1010
  # ignore clicks outside the expected axes
818
1011
  # (note that the color bar is a different axes)
819
1012
  if event.inaxes == self.ax:
@@ -11,6 +11,7 @@
11
11
 
12
12
  from scipy import ndimage
13
13
  import numpy as np
14
+ from tqdm import tqdm
14
15
 
15
16
  from .dilatemask import dilatemask
16
17
  from .interpolation_x import interpolation_x
@@ -18,7 +19,7 @@ from .interpolation_y import interpolation_y
18
19
  from .interpolation_a import interpolation_a
19
20
 
20
21
 
21
- def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, degree=None, debug=False):
22
+ def interpolate(data, mask_crfound, dilation=0, interp_method=None, npoints=None, degree=None, debug=False):
22
23
  """Interpolate pixels identified in a mask.
23
24
 
24
25
  The original data and mask are not modified. A copy of both
@@ -29,8 +30,8 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
29
30
  data : 2D numpy.ndarray
30
31
  The image data array to be processed.
31
32
  mask_crfound : 2D numpy.ndarray of bool
32
- A boolean mask array indicating which pixels are affected by
33
- cosmic rays.
33
+ A boolean mask array indicating which pixels are flagged
34
+ and need to be interpolated (True = pixel to be fixed).
34
35
  dilation : int, optional
35
36
  The number of pixels to dilate the masked pixels before
36
37
  interpolation.
@@ -48,7 +49,7 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
48
49
  The degree of the polynomial to fit. This parameter is
49
50
  relevant for 'x' and 'y' methods.
50
51
  debug : bool, optional
51
- If True, print debug information.
52
+ If True, print debug information and enable tqdm progress bar.
52
53
 
53
54
  Returns
54
55
  -------
@@ -86,13 +87,14 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
86
87
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
87
88
  cr_labels, num_features = ndimage.label(updated_mask_crfound, structure=structure)
88
89
  if debug:
89
- print(f"Number of cosmic ray pixels to be cleaned: {np.sum(updated_mask_crfound)}")
90
- print(f"Number of cosmic rays (grouped pixels)...: {num_features}")
90
+ sdum = str(np.sum(updated_mask_crfound))
91
+ print(f"Number of cosmic ray pixels to be cleaned: {sdum}")
92
+ print(f"Number of cosmic rays (grouped pixels)...: {num_features:>{len(sdum)}}")
91
93
 
92
94
  # Fix cosmic rays using the specified interpolation method
93
95
  cleaned_data = data.copy()
94
96
  num_cr_cleaned = 0
95
- for cr_index in range(1, num_features + 1):
97
+ for cr_index in tqdm(range(1, num_features + 1), disable=not debug):
96
98
  if interp_method in ["x", "y"]:
97
99
  if 2 * npoints <= degree:
98
100
  raise ValueError("2*npoints must be greater than degree for polynomial interpolation.")
@@ -131,6 +133,6 @@ def cleanest(data, mask_crfound, dilation=0, interp_method=None, npoints=None, d
131
133
  raise ValueError(f"Unknown interpolation method: {interp_method}")
132
134
 
133
135
  if debug:
134
- print(f"Number of cosmic rays cleaned............: {num_cr_cleaned}")
136
+ print(f"Number of cosmic rays cleaned............: {num_cr_cleaned:>{len(sdum)}}")
135
137
 
136
138
  return cleaned_data, mask_fixed
@@ -0,0 +1,57 @@
1
+ #
2
+ # Copyright 2025 Universidad Complutense de Madrid
3
+ #
4
+ # This file is part of teareduce
5
+ #
6
+ # SPDX-License-Identifier: GPL-3.0+
7
+ # License-Filename: LICENSE.txt
8
+ #
9
+
10
+ """Execute LACosmic algorithm on a padded image."""
11
+
12
+ from ccdproc import cosmicray_lacosmic
13
+ import numpy as np
14
+
15
+
16
+ def lacosmicpad(pad_width, **kwargs):
17
+ """Execute LACosmic algorithm on a padded array.
18
+
19
+ This function pads the input image array before applying the LACosmic
20
+ cosmic ray cleaning algorithm. After processing, the padding is removed
21
+ to return an array of the original size.
22
+
23
+ The padding helps to mitigate edge effects that can occur during the
24
+ cosmic ray detection and cleaning process.
25
+
26
+ Apart from the `pad_width` parameter, all other keyword arguments
27
+ are passed directly to the `cosmicray_lacosmic` function from the
28
+ `ccdproc` package.
29
+
30
+ Parameters
31
+ ----------
32
+ pad_width : int
33
+ Width of the padding to be applied to the image before executing
34
+ the LACosmic algorithm.
35
+ **kwargs : dict
36
+ Keyword arguments to be passed to the `cosmicray_lacosmic` function.
37
+
38
+ Returns
39
+ -------
40
+ clean_array : 2D numpy.ndarray
41
+ The cleaned image array after applying the LACosmic algorithm with padding.
42
+ mask_array : 2D numpy.ndarray of bool
43
+ The mask array indicating detected cosmic rays.
44
+ """
45
+ if "ccd" not in kwargs:
46
+ raise ValueError("The 'ccd' keyword argument must be provided.")
47
+ array = kwargs.pop("ccd")
48
+ if not isinstance(array, np.ndarray):
49
+ raise TypeError("The 'ccd' keyword argument must be a numpy ndarray.")
50
+ # Pad the array
51
+ padded_array = np.pad(array, pad_width, mode="reflect")
52
+ # Apply LACosmic algorithm to the padded array
53
+ cleaned_padded_array, mask_padded_array = cosmicray_lacosmic(ccd=padded_array, **kwargs)
54
+ # Remove padding
55
+ cleaned_array = cleaned_padded_array[pad_width:-pad_width, pad_width:-pad_width]
56
+ mask_array = mask_padded_array[pad_width:-pad_width, pad_width:-pad_width]
57
+ return cleaned_array, mask_array
@@ -0,0 +1,58 @@
1
+ #
2
+ # Copyright 2025 Universidad Complutense de Madrid
3
+ #
4
+ # This file is part of teareduce
5
+ #
6
+ # SPDX-License-Identifier: GPL-3.0+
7
+ # License-Filename: LICENSE.txt
8
+ #
9
+
10
+ """Merge peak and tail masks for cosmic ray cleaning."""
11
+
12
+ import numpy as np
13
+ from scipy import ndimage
14
+
15
+
16
+ def merge_peak_tail_masks(mask_peaks, mask_tails):
17
+ """Merge peak and tail masks for cosmic ray cleaning.
18
+
19
+ Tail pixels are preserved only if they correspond to CR features
20
+ that are also present in the peak mask.
21
+
22
+ Parameters
23
+ ----------
24
+ mask_peaks : ndarray
25
+ Boolean array indicating the pixels identified as cosmic ray peaks.
26
+ mask_tails : ndarray
27
+ Boolean array indicating the pixels identified as cosmic ray tails.
28
+
29
+ Returns
30
+ -------
31
+ merged_mask : ndarray
32
+ Boolean array indicating the merged cosmic ray mask.
33
+ """
34
+ # check that input masks are numpy arrays
35
+ if not isinstance(mask_peaks, np.ndarray) or not isinstance(mask_tails, np.ndarray):
36
+ raise TypeError("Input masks must be numpy arrays.")
37
+ # check that input masks have the same shape
38
+ if mask_peaks.shape != mask_tails.shape:
39
+ raise ValueError("Input masks must have the same shape.")
40
+ # check that input masks are boolean arrays
41
+ if mask_peaks.dtype != bool or mask_tails.dtype != bool:
42
+ raise TypeError("Input masks must be boolean arrays.")
43
+
44
+ # find structures in tail mask
45
+ structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
46
+ cr_labels_tails, num_crs_tails = ndimage.label(mask_tails, structure=structure)
47
+ # generate mask of ones at peak pixels
48
+ mask_peaks_ones = np.zeros(mask_peaks.shape, dtype=float)
49
+ mask_peaks_ones[mask_peaks] = 1.0
50
+ # preserve only those tail pixels that are flagged as peaks
51
+ cr_labels_tails_preserved = mask_peaks_ones * cr_labels_tails
52
+ # generate new mask with preserved tail pixels
53
+ merged_mask = np.zeros_like(mask_peaks, dtype=bool)
54
+ for icr in np.unique(cr_labels_tails_preserved):
55
+ if icr > 0:
56
+ merged_mask[cr_labels_tails == icr] = True
57
+
58
+ return merged_mask
@@ -116,11 +116,11 @@ class ModalProgressBar:
116
116
 
117
117
  elapsed_str = self._format_time(elapsed)
118
118
  eta_str = self._format_time(eta_seconds)
119
+ total_str = self._format_time(elapsed + eta_seconds)
119
120
  rate_str = f"{rate:.2f} CR/s" if rate >= 1 else f"{1/rate:.2f} s/CR"
120
121
 
121
122
  self.status_label.config(text=f"{self.current}/{self.total} ({percentage:.1f}%) | {rate_str}")
122
- self.time_label.config(text=f"Elapsed: {elapsed_str} | ETA: {eta_str}")
123
-
123
+ self.time_label.config(text=f"Expected Total: {total_str} | Elapsed: {elapsed_str} | ETA: {eta_str}")
124
124
  self.window.update_idletasks()
125
125
  self.window.update()
126
126
 
@@ -101,10 +101,21 @@ class ParameterEditor:
101
101
  bold_font = default_font.copy()
102
102
  bold_font.configure(weight="bold", size=default_font.cget("size") + 2)
103
103
  subtitle_label = tk.Label(main_frame, text="L.A.Cosmic Parameters", font=bold_font)
104
- subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
104
+ subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
105
105
  row += 1
106
106
 
107
107
  # Create labels and entry fields for each parameter.
108
+ bold_font_subheader = default_font.copy()
109
+ bold_font_subheader.configure(weight="bold", size=default_font.cget("size") + 1)
110
+ label = tk.Label(main_frame, text="Parameter", font=bold_font_subheader, anchor="w", fg="gray")
111
+ label.grid(row=row, column=0, sticky="e", pady=0)
112
+ label = tk.Label(main_frame, text="Run 1", font=bold_font_subheader, anchor="w", fg="gray", width=10)
113
+ label.grid(row=row, column=1, sticky="w", padx=10, pady=0)
114
+ label = tk.Label(main_frame, text="Run 2", font=bold_font_subheader, anchor="w", fg="gray", width=10)
115
+ label.grid(row=row, column=2, sticky="w", padx=10, pady=0)
116
+ label = tk.Label(main_frame, text="Type", font=bold_font_subheader, anchor="w", fg="gray", width=10)
117
+ label.grid(row=row, column=3, sticky="w", pady=0)
118
+ row += 1
108
119
  # Note: here we are using entry_vars to trace changes in the entries
109
120
  # so that we can update the color of run2 entries if they differ from run1.
110
121
  self.entry_vars = {}
@@ -142,7 +153,7 @@ class ParameterEditor:
142
153
 
143
154
  # Subtitle for additional parameters
144
155
  subtitle_label = tk.Label(main_frame, text="Additional Parameters", font=bold_font)
145
- subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
156
+ subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
146
157
  row += 1
147
158
 
148
159
  # Dilation label and entry
@@ -177,7 +188,7 @@ class ParameterEditor:
177
188
 
178
189
  # Subtitle for region to be examined
179
190
  subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=bold_font)
180
- subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
191
+ subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 10))
181
192
  row += 1
182
193
 
183
194
  # Region to be examined label and entries
@@ -12,7 +12,7 @@
12
12
  import numpy as np
13
13
 
14
14
 
15
- from ..cleanest.cleanest import cleanest
15
+ from ..cleanest.interpolate import interpolate
16
16
 
17
17
 
18
18
  def test_cleanest_no_cr():
@@ -23,7 +23,7 @@ def test_cleanest_no_cr():
23
23
  mask_crfound = np.zeros_like(data, dtype=bool)
24
24
 
25
25
  for interp_method in ['x', 'y', 's', 'd', 'm']:
26
- cleaned_data, mask_fixed = cleanest(data, mask_crfound, interp_method=interp_method, npoints=2, degree=1)
26
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound, interp_method=interp_method, npoints=2, degree=1)
27
27
 
28
28
  assert np.array_equal(cleaned_data, data), "Data should remain unchanged when no cosmic rays are present."
29
29
  assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged when no cosmic rays are present."
@@ -38,7 +38,7 @@ def test_cleanest_interpolation_x():
38
38
  [False, False, True, False, False],
39
39
  [False, False, True, False, False]], dtype=bool)
40
40
 
41
- cleaned_data, mask_fixed = cleanest(data, mask_crfound,
41
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound,
42
42
  interp_method='x', npoints=2, degree=1)
43
43
 
44
44
  expected_data = np.array([[1, 1, 1, 1, 1],
@@ -62,8 +62,8 @@ def test_cleanest_interpolation_y():
62
62
  [False, False, False],
63
63
  [False, False, False]], dtype=bool)
64
64
 
65
- cleaned_data, mask_fixed = cleanest(data, mask_crfound,
66
- interp_method='y', npoints=2, degree=1)
65
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound,
66
+ interp_method='y', npoints=2, degree=1)
67
67
 
68
68
  expected_data = np.array([[1, 2, 3],
69
69
  [1, 2, 3],
@@ -83,8 +83,8 @@ def test_cleanest_interpolation_surface():
83
83
  [False, True, False],
84
84
  [False, False, False]], dtype=bool)
85
85
 
86
- cleaned_data, mask_fixed = cleanest(data, mask_crfound,
87
- interp_method='s', npoints=1)
86
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound,
87
+ interp_method='s', npoints=1)
88
88
 
89
89
  expected_data = np.array([[1, 2, 3],
90
90
  [4, 5, 6],
@@ -102,8 +102,8 @@ def test_cleanest_interpolation_median():
102
102
  [False, True, False],
103
103
  [False, False, False]], dtype=bool)
104
104
 
105
- cleaned_data, mask_fixed = cleanest(data, mask_crfound,
106
- interp_method='d', npoints=1)
105
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound,
106
+ interp_method='d', npoints=1)
107
107
 
108
108
  expected_data = np.array([[1, 2, 3],
109
109
  [4, 5, 6],
@@ -121,8 +121,8 @@ def test_cleanest_interpolation_mean():
121
121
  [False, True, False],
122
122
  [False, False, False]], dtype=bool)
123
123
 
124
- cleaned_data, mask_fixed = cleanest(data, mask_crfound,
125
- interp_method='m', npoints=1)
124
+ cleaned_data, mask_fixed = interpolate(data, mask_crfound,
125
+ interp_method='m', npoints=1)
126
126
 
127
127
  expected_data = np.array([[1, 2, 3],
128
128
  [4, 5, 6],
teareduce/version.py CHANGED
@@ -9,7 +9,7 @@
9
9
  #
10
10
  """Module to define the version of the teareduce package."""
11
11
 
12
- VERSION = '0.5.5'
12
+ VERSION = '0.5.7'
13
13
 
14
14
 
15
15
  def main():
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teareduce
3
- Version: 0.5.5
3
+ Version: 0.5.7
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
@@ -15,35 +15,37 @@ teareduce/sdistortion.py,sha256=5ZsZn4vD5Sw2aoqO8-NIOH7H89Zmh7ZDkow6YbAotHU,5916
15
15
  teareduce/simulateccdexposure.py,sha256=cdbpca6GVuM3d7R1LGzlIZZvjTq_jzrlkk_Cli7aouQ,24636
16
16
  teareduce/sliceregion.py,sha256=Jdf8XvmGaY_vaY1cneTaRtSOYPxpUsJm9cXJDDMa0YM,18626
17
17
  teareduce/statsummary.py,sha256=VTNAnBV8z6suqiyB2Lhw3YjUUOjlmwUPX3enkOKRF54,5422
18
- teareduce/version.py,sha256=x8qFVHj0LWtGzf-qBtcwzyAnbALRdsIJ4c4C_GQISJc,419
18
+ teareduce/version.py,sha256=oqn_gabhd81ghdha2yfonHK150M_SY20r86f1CEW9bg,419
19
19
  teareduce/wavecal.py,sha256=2MewWz5Y3ms41c305UtWOM_LaLNdk1mugDXCtzf-fSw,68586
20
20
  teareduce/write_array_to_fits.py,sha256=kWDrEH9coJ1yIu56oQJpWtDqJL4c8HGmssE9jle4e94,617
21
21
  teareduce/zscale.py,sha256=SDgmcDD2N5GvDn46JADCgTQJPBF_N_jSKMHoeTz9Nsw,1152
22
- teareduce/cleanest/__init__.py,sha256=3O-eptEAQareN504tLALMrJVQPGCuBx7YTfQGqMKjmc,205
23
- teareduce/cleanest/__main__.py,sha256=_wXGgwq62ete5-DlNDsKkZP0kATAVFVwTVrG9EspkU8,3298
22
+ teareduce/cleanest/__init__.py,sha256=hHFtV6uBx6HAI8LFUDli0urznTUzMotCW9J_4EKGCXc,293
23
+ teareduce/cleanest/__main__.py,sha256=cMzvNWNe2fuTGHLtT3hOc9-NAcMXDZUqHN0SSHWWDnc,5977
24
24
  teareduce/cleanest/centerchildparent.py,sha256=wHxOvNrrQ-KBLZAbtQ9bJAxYhGOzqYBF4LgdIQk7UF8,1285
25
- teareduce/cleanest/cleanest.py,sha256=hDC_3jF9ovHCX8IRAPe9eEGFWt_LeVI5rzS7atK7oWI,5025
26
- teareduce/cleanest/cosmicraycleanerapp.py,sha256=C-lGOREVXrlaqJsjEuJ66xVNx0z8axXq5f5GQh9dpMA,41226
25
+ teareduce/cleanest/cosmicraycleanerapp.py,sha256=FKeQYCUhvod6HDSOqZN79QhVoujhC2_iQsyP1tF3P9o,49726
27
26
  teareduce/cleanest/definitions.py,sha256=HLv41cuhUOUyEicRHea0LRTv4Wagm24miSxFt4cZN3g,2546
28
27
  teareduce/cleanest/dilatemask.py,sha256=I5tHAv5VEO6V0Wed8Ar20uLt4F9P-tgjmLL5BAaFvgM,1276
29
28
  teareduce/cleanest/find_closest_true.py,sha256=mWdIvhipzAXDRKfePDrP7f0lP4U48cckpHiKwiB4jHI,1320
30
29
  teareduce/cleanest/imagedisplay.py,sha256=820Vn-Q0bJyHicOBsxDmfAZxuGOFepEEsm0LTxlPJjc,5848
30
+ teareduce/cleanest/interpolate.py,sha256=qlz4SHw89ahBRx1VHBMkd0IEpFkd96P6FVfsw_LoQDA,5194
31
31
  teareduce/cleanest/interpolation_a.py,sha256=zE4VIrC41vapf0Vx9qmh1oacw2qkJwcuMnV3JpSDW8Y,4007
32
32
  teareduce/cleanest/interpolation_x.py,sha256=D5hKbobT6lJk18XktP3PXhzEN12zqed6U18yfQ-reLQ,3744
33
33
  teareduce/cleanest/interpolation_y.py,sha256=O6yw5nKKlTdV6qsP1Ku6CwGhXB6o3j0_YQirXyILi8c,3759
34
34
  teareduce/cleanest/interpolationeditor.py,sha256=EksuNwKOM79MyGlAgxb9j-v7pIFXV8QOcyZDLVQlQjU,13221
35
- teareduce/cleanest/modalprogressbar.py,sha256=9anz3js_y_9JLjPyA7XxEsLpVwuMN_CTlnQnhf7m3uo,6355
36
- teareduce/cleanest/parametereditor.py,sha256=IdaX6_VPicwnYw3dvRA0tKoTZYb6Gp-AZB5ksFClv1o,13390
35
+ teareduce/cleanest/lacosmicpad.py,sha256=xWaxifoF_B82Gm5czuJmL7Y4Cv0W___LlvcpH633bAo,2047
36
+ teareduce/cleanest/mergemasks.py,sha256=1Vq6Wqn6DClxSAvHwy6QrJGszA8nkmoMHITyprSykHI,2072
37
+ teareduce/cleanest/modalprogressbar.py,sha256=uwd-p92PvOVJnbXd-B8DRcBZ--keKpr4ZN9PLeqm1Ws,6449
38
+ teareduce/cleanest/parametereditor.py,sha256=_0baFbkIUcPyF-teFIRMt1MpA4IzDZc7VHNh1h0qN4A,14229
37
39
  teareduce/cleanest/reviewcosmicray.py,sha256=_qO9ifkAMqB4Wuvq2wIYZ8_DUniB2lWcxn9l404hXnY,28210
38
40
  teareduce/cookbook/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
39
41
  teareduce/cookbook/get_cookbook_file.py,sha256=vde-iNii2lm1QII8GmLRsFsKNxkdsd7njCBE-8Z7io0,1088
40
42
  teareduce/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
41
- teareduce/tests/test_cleanest.py,sha256=0uiun1Uloh3rt5ppkv1aG04KUbOJvYZ9C5BvqNizLuI,5562
43
+ teareduce/tests/test_cleanest.py,sha256=6dRqkw1RQMKsFrC8cEweMvTD6wXhiDv3P4PS57-HEqI,5598
42
44
  teareduce/tests/test_sliceregion.py,sha256=S7Zoh2eEBFIEbfsXgWBEMCf7pottjw2oLhqlZJQkAwg,3785
43
45
  teareduce/tests/test_version.py,sha256=mKLnbXyvVNc1pATq5PxR8qeoFMPAFL_GilFV6IHLOi0,172
44
- teareduce-0.5.5.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
45
- teareduce-0.5.5.dist-info/METADATA,sha256=MIifKnlyWPEQyuxUG3OaqqRnydUaE7JYhcBIB-XDu7Q,3113
46
- teareduce-0.5.5.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
47
- teareduce-0.5.5.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
48
- teareduce-0.5.5.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
49
- teareduce-0.5.5.dist-info/RECORD,,
46
+ teareduce-0.5.7.dist-info/licenses/LICENSE.txt,sha256=OXLcl0T2SZ8Pmy2_dmlvKuetivmyPd5m1q-Gyd-zaYY,35149
47
+ teareduce-0.5.7.dist-info/METADATA,sha256=q725fGixcsrRvW2iS9-QRgBPi54dZqjOILxmzEDBShs,3113
48
+ teareduce-0.5.7.dist-info/WHEEL,sha256=_zCd3N1l69ArxyTb8rzEoP9TpbYXkqRFSNOD5OuxnTs,91
49
+ teareduce-0.5.7.dist-info/entry_points.txt,sha256=6yBvig5jTL2ugqz5SF767AiszzrHKGRASsX1II84kqA,66
50
+ teareduce-0.5.7.dist-info/top_level.txt,sha256=7OkwtX9zNRkGJ7ACgjk4ESgC74qUYcS5O2qcO0v-Si4,10
51
+ teareduce-0.5.7.dist-info/RECORD,,