teareduce 0.5.8__py3-none-any.whl → 0.6.0__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.
@@ -13,7 +13,14 @@ import tkinter as tk
13
13
  from tkinter import messagebox
14
14
  from tkinter import simpledialog
15
15
 
16
- from maskfill import maskfill
16
+ try:
17
+ from maskfill import maskfill
18
+ except ModuleNotFoundError as e:
19
+ raise ModuleNotFoundError(
20
+ "The 'teareduce.cleanest' module requires the 'ccdproc' and 'maskfill' packages. "
21
+ "Please install teareduce with the 'cleanest' extra dependencies: "
22
+ "`pip install teareduce[cleanest]`."
23
+ ) from e
17
24
  import matplotlib.pyplot as plt
18
25
  from matplotlib.backend_bases import key_press_handler
19
26
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
@@ -21,15 +28,19 @@ import numpy as np
21
28
  from rich import print
22
29
 
23
30
  from .centerchildparent import center_on_parent
31
+ from .definitions import DEFAULT_TK_WINDOW_SIZE_X
32
+ from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
24
33
  from .definitions import MAX_PIXEL_DISTANCE_TO_CR
25
34
  from .imagedisplay import ImageDisplay
26
35
  from .interpolation_a import interpolation_a
27
36
  from .interpolation_x import interpolation_x
28
37
  from .interpolation_y import interpolation_y
38
+ from .trackedbutton import TrackedTkButton
29
39
 
30
40
  from ..imshow import imshow
31
41
  from ..sliceregion import SliceRegion2D
32
42
  from ..zscale import zscale
43
+ from ..version import VERSION
33
44
 
34
45
  import matplotlib
35
46
 
@@ -42,6 +53,8 @@ class ReviewCosmicRay(ImageDisplay):
42
53
  def __init__(
43
54
  self,
44
55
  root,
56
+ root_width,
57
+ root_height,
45
58
  data,
46
59
  auxdata,
47
60
  cleandata_lacosmic,
@@ -52,6 +65,10 @@ class ReviewCosmicRay(ImageDisplay):
52
65
  last_dilation=None,
53
66
  last_npoints=None,
54
67
  last_degree=None,
68
+ last_maskfill_size=None,
69
+ last_maskfill_operator=None,
70
+ last_maskfill_smooth=None,
71
+ last_maskfill_verbose=None,
55
72
  ):
56
73
  """Initialize the review window.
57
74
 
@@ -59,6 +76,10 @@ class ReviewCosmicRay(ImageDisplay):
59
76
  ----------
60
77
  root : tk.Toplevel
61
78
  The parent Tkinter root window.
79
+ root_width : int
80
+ The width of the root window. The review window is scaled accordingly.
81
+ root_height : int
82
+ The height of the root window. The review window is scaled accordingly.
62
83
  data : 2D numpy array
63
84
  The original image data.
64
85
  auxdata : 2D numpy array or None
@@ -83,6 +104,14 @@ class ReviewCosmicRay(ImageDisplay):
83
104
  The last used number of points parameter for interpolation.
84
105
  last_degree : int or None, optional
85
106
  The last used degree parameter for interpolation.
107
+ last_maskfill_size : int or None, optional
108
+ The last used maskfill size parameter.
109
+ last_maskfill_operator : str or None, optional
110
+ The last used maskfill operator parameter.
111
+ last_maskfill_smooth : bool or None, optional
112
+ The last used maskfill smooth parameter.
113
+ last_maskfill_verbose : bool or None, optional
114
+ The last used maskfill verbose parameter.
86
115
 
87
116
  Methods
88
117
  -------
@@ -119,6 +148,10 @@ class ReviewCosmicRay(ImageDisplay):
119
148
  ----------
120
149
  root : tk.Toplevel
121
150
  The parent Tkinter root window.
151
+ factor_width : float
152
+ The scaling factor for the width of the review window.
153
+ factor_height : float
154
+ The scaling factor for the height of the review window.
122
155
  data : 2D numpy array
123
156
  The original image data.
124
157
  auxdata : 2D numpy array or None
@@ -139,19 +172,33 @@ class ReviewCosmicRay(ImageDisplay):
139
172
  Degree parameter for interpolation.
140
173
  npoints : int
141
174
  Number of points parameter for interpolation.
175
+ maskfill_size : int
176
+ The last used maskfill size parameter.
177
+ maskfill_operator : str
178
+ The last used maskfill operator parameter.
179
+ maskfill_smooth : bool
180
+ The last used maskfill smooth parameter.
181
+ maskfill_verbose : bool
182
+ The last used maskfill verbose parameter.
142
183
  last_dilation : int or None
143
184
  The last used dilation parameter employed after L.A.Cosmic
144
185
  detection.
145
186
  """
146
187
  self.root = root
147
- self.root.title("Review Cosmic Rays")
188
+ self.root.title(f"Review Cosmic Rays (TEA version {VERSION})")
189
+ self.factor_width = root_width / DEFAULT_TK_WINDOW_SIZE_X
190
+ self.factor_height = root_height / DEFAULT_TK_WINDOW_SIZE_Y
148
191
  self.auxdata = auxdata
149
192
  if self.auxdata is not None:
150
193
  # self.root.geometry("1000x700+100+100") # This does not work in Fedora
151
- self.root.minsize(1000, 700)
194
+ window_width = int(1000 * self.factor_width + 0.5)
195
+ window_height = int(700 * self.factor_height + 0.5)
196
+ self.root.minsize(window_width, window_height)
152
197
  else:
153
- # self.root.geometry("800x700+100+100") # This does not work in Fedora
154
- self.root.minsize(900, 700)
198
+ # self.root.geometry("900x700+100+100") # This does not work in Fedora
199
+ window_width = int(900 * self.factor_width + 0.5)
200
+ window_height = int(700 * self.factor_height + 0.5)
201
+ self.root.minsize(window_width, window_height)
155
202
  self.root.update_idletasks()
156
203
  self.root.geometry("+100+100")
157
204
  self.data = data
@@ -164,6 +211,10 @@ class ReviewCosmicRay(ImageDisplay):
164
211
  self.first_plot = True
165
212
  self.degree = last_degree if last_degree is not None else 1
166
213
  self.npoints = last_npoints if last_npoints is not None else 2
214
+ self.maskfill_size = last_maskfill_size if last_maskfill_size is not None else 3
215
+ self.maskfill_operator = last_maskfill_operator if last_maskfill_operator is not None else "median"
216
+ self.maskfill_smooth = last_maskfill_smooth if last_maskfill_smooth is not None else True
217
+ self.maskfill_verbose = last_maskfill_verbose if last_maskfill_verbose is not None else False
167
218
  self.last_dilation = last_dilation
168
219
  # Make a copy of the original labels to allow pixel re-marking
169
220
  self.cr_labels_original = self.cr_labels.copy()
@@ -180,29 +231,74 @@ class ReviewCosmicRay(ImageDisplay):
180
231
 
181
232
  def create_widgets(self):
182
233
  """Create the GUI widgets for the review window."""
234
+ # Define instance of TrackedTkButton, that facilitates to show help information
235
+ # for each button displayed in the current application window.
236
+ tkbutton = TrackedTkButton(self.root)
237
+
183
238
  # Row 1 of buttons
184
239
  self.button_frame1 = tk.Frame(self.root)
185
240
  self.button_frame1.pack(pady=5)
186
- self.ndeg_label = tk.Button(
187
- self.button_frame1, text=f"Npoints={self.npoints}, Degree={self.degree}", command=self.set_ndeg
241
+ self.ndeg_label = tkbutton.new(
242
+ self.button_frame1,
243
+ text=f"Npoints={self.npoints}, Degree={self.degree}",
244
+ command=self.set_ndeg,
245
+ help_text="Set the Npoints and Degree parameters for interpolation.",
246
+ alttext="Npoints=?, Degree=?",
188
247
  )
189
248
  self.ndeg_label.pack(side=tk.LEFT, padx=5)
190
- self.remove_crosses_button = tk.Button(self.button_frame1, text="remove all x's", command=self.remove_crosses)
249
+ self.maskfill_button = tkbutton.new(
250
+ self.button_frame1,
251
+ text="Maskfill params.",
252
+ command=self.set_maskfill_params,
253
+ help_text="Set the parameters for the maskfill method.",
254
+ )
255
+ self.maskfill_button.pack(side=tk.LEFT, padx=5)
256
+ self.remove_crosses_button = tkbutton.new(
257
+ self.button_frame1,
258
+ text="remove all x's",
259
+ command=self.remove_crosses,
260
+ help_text="Remove all cross marks from the image.",
261
+ )
191
262
  self.remove_crosses_button.pack(side=tk.LEFT, padx=5)
192
- self.restore_cr_button = tk.Button(self.button_frame1, text="[r]estore CR data", command=self.restore_cr)
263
+ self.restore_cr_button = tkbutton.new(
264
+ self.button_frame1,
265
+ text="restore CR",
266
+ command=self.restore_cr,
267
+ help_text="Restore current cosmic ray pixels to their original values.",
268
+ )
193
269
  self.restore_cr_button.pack(side=tk.LEFT, padx=5)
194
270
  self.restore_cr_button.config(state=tk.DISABLED)
195
- self.next_button = tk.Button(self.button_frame1, text="[c]ontinue", command=self.continue_cr)
271
+ self.next_button = tkbutton.new(
272
+ self.button_frame1,
273
+ text="[c]ontinue",
274
+ command=self.continue_cr,
275
+ help_text="Continue to the next cosmic ray.",
276
+ )
196
277
  self.next_button.pack(side=tk.LEFT, padx=5)
197
- self.exit_button = tk.Button(self.button_frame1, text="[e]xit review", command=self.exit_review)
278
+ self.exit_button = tkbutton.new(
279
+ self.button_frame1,
280
+ text="[e]xit review",
281
+ command=self.exit_review,
282
+ help_text="Exit the cosmic ray review process.",
283
+ )
198
284
  self.exit_button.pack(side=tk.LEFT, padx=5)
199
285
 
200
286
  # Row 2 of buttons
201
287
  self.button_frame2 = tk.Frame(self.root)
202
288
  self.button_frame2.pack(pady=5)
203
- self.interp_x_button = tk.Button(self.button_frame2, text="[x] interp.", command=self.interp_x)
289
+ self.interp_x_button = tkbutton.new(
290
+ self.button_frame2,
291
+ text="[x] interp.",
292
+ command=self.interp_x,
293
+ help_text="Perform X-interpolation for the current cosmic ray.",
294
+ )
204
295
  self.interp_x_button.pack(side=tk.LEFT, padx=5)
205
- self.interp_y_button = tk.Button(self.button_frame2, text="[y] interp.", command=self.interp_y)
296
+ self.interp_y_button = tkbutton.new(
297
+ self.button_frame2,
298
+ text="[y] interp.",
299
+ command=self.interp_y,
300
+ help_text="Perform Y-interpolation for the current cosmic ray.",
301
+ )
206
302
  self.interp_y_button.pack(side=tk.LEFT, padx=5)
207
303
  # it is important to use lambda here to pass the method argument correctly
208
304
  # (avoiding the execution of the function at button creation time, which would happen
@@ -211,23 +307,51 @@ class ReviewCosmicRay(ImageDisplay):
211
307
  # the function is trying to deactivate the buttons before they are created, which
212
308
  # would lead to an error; in addition, since I have two buttons calling the same function
213
309
  # with different arguments, using lambda allows to differentiate them)
214
- self.interp_s_button = tk.Button(
215
- self.button_frame2, text="[s]urface interp.", command=lambda: self.interp_a("surface")
310
+ self.interp_s_button = tkbutton.new(
311
+ self.button_frame2,
312
+ text="[s]urface interp.",
313
+ command=lambda: self.interp_a("surface"),
314
+ help_text="Perform surface interpolation for the current cosmic ray.",
216
315
  )
217
316
  self.interp_s_button.pack(side=tk.LEFT, padx=5)
218
- self.interp_d_button = tk.Button(self.button_frame2, text="me[d]ian", command=lambda: self.interp_a("median"))
317
+ self.interp_d_button = tkbutton.new(
318
+ self.button_frame2,
319
+ text="me[d]ian",
320
+ command=lambda: self.interp_a("median"),
321
+ help_text="Perform median interpolation for the current cosmic ray.",
322
+ )
219
323
  self.interp_d_button.pack(side=tk.LEFT, padx=5)
220
- self.interp_m_button = tk.Button(self.button_frame2, text="[m]ean", command=lambda: self.interp_a("mean"))
324
+ self.interp_m_button = tkbutton.new(
325
+ self.button_frame2,
326
+ text="[m]ean",
327
+ command=lambda: self.interp_a("mean"),
328
+ help_text="Perform mean interpolation for the current cosmic ray.",
329
+ )
221
330
  self.interp_m_button.pack(side=tk.LEFT, padx=5)
222
- self.interp_l_button = tk.Button(self.button_frame2, text="[l]acosmic", command=self.use_lacosmic)
331
+ self.interp_l_button = tkbutton.new(
332
+ self.button_frame2,
333
+ text="[l]acosmic",
334
+ command=self.use_lacosmic,
335
+ help_text="Use L.A.Cosmic interpolation for the current cosmic ray.",
336
+ )
223
337
  self.interp_l_button.pack(side=tk.LEFT, padx=5)
224
338
  if self.last_dilation is not None and self.last_dilation > 0:
225
339
  self.interp_l_button.config(state=tk.DISABLED)
226
340
  if self.cleandata_lacosmic is None:
227
341
  self.interp_l_button.config(state=tk.DISABLED)
228
- self.interp_maskfill_button = tk.Button(self.button_frame2, text="mas[k]fill", command=self.use_maskfill)
342
+ self.interp_maskfill_button = tkbutton.new(
343
+ self.button_frame2,
344
+ text="mask[f]ill",
345
+ command=self.use_maskfill,
346
+ help_text="Perform maskfill interpolation for the current cosmic ray.",
347
+ )
229
348
  self.interp_maskfill_button.pack(side=tk.LEFT, padx=5)
230
- self.interp_aux_button = tk.Button(self.button_frame2, text="[a]ux. data", command=self.use_auxdata)
349
+ self.interp_aux_button = tkbutton.new(
350
+ self.button_frame2,
351
+ text="[a]ux. data",
352
+ command=self.use_auxdata,
353
+ help_text="Use auxiliary data for interpolation of the current cosmic ray.",
354
+ )
231
355
  self.interp_aux_button.pack(side=tk.LEFT, padx=5)
232
356
  if self.auxdata is None:
233
357
  self.interp_aux_button.config(state=tk.DISABLED)
@@ -236,20 +360,53 @@ class ReviewCosmicRay(ImageDisplay):
236
360
  self.button_frame3 = tk.Frame(self.root)
237
361
  self.button_frame3.pack(pady=5)
238
362
  vmin, vmax = zscale(self.data)
239
- self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
363
+ self.vmin_button = tkbutton.new(
364
+ self.button_frame3,
365
+ text=f"vmin: {vmin:.2f}",
366
+ command=self.set_vmin,
367
+ help_text="Set the minimum value for the display scale.",
368
+ alttext="vmin: ??",
369
+ )
240
370
  self.vmin_button.pack(side=tk.LEFT, padx=5)
241
- self.vmax_button = tk.Button(self.button_frame3, text=f"vmax: {vmax:.2f}", command=self.set_vmax)
371
+ self.vmax_button = tkbutton.new(
372
+ self.button_frame3,
373
+ text=f"vmax: {vmax:.2f}",
374
+ command=self.set_vmax,
375
+ help_text="Set the maximum value for the display scale.",
376
+ alttext="vmax: ??",
377
+ )
242
378
  self.vmax_button.pack(side=tk.LEFT, padx=5)
243
- self.set_minmax_button = tk.Button(self.button_frame3, text="minmax [,]", command=self.set_minmax)
379
+ self.set_minmax_button = tkbutton.new(
380
+ self.button_frame3,
381
+ text="minmax [,]",
382
+ command=self.set_minmax,
383
+ help_text="Set the display scale to the minimum and maximum data values.",
384
+ )
244
385
  self.set_minmax_button.pack(side=tk.LEFT, padx=5)
245
- self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
386
+ self.set_zscale_button = tkbutton.new(
387
+ self.button_frame3,
388
+ text="zscale [/]",
389
+ command=self.set_zscale,
390
+ help_text="Set the display scale using zscale.",
391
+ )
246
392
  self.set_zscale_button.pack(side=tk.LEFT, padx=5)
393
+ self.help_button = tkbutton.new(
394
+ self.button_frame3,
395
+ text="Help",
396
+ command=tkbutton.show_help,
397
+ help_text="Show help information for all buttons.",
398
+ )
399
+ self.help_button.pack(side=tk.LEFT, padx=5)
247
400
 
248
401
  # Figure
249
402
  if self.auxdata is not None:
250
- self.fig, (self.ax_aux, self.ax) = plt.subplots(ncols=2, figsize=(11, 5.5), constrained_layout=True)
403
+ self.fig, (self.ax, self.ax_aux) = plt.subplots(
404
+ ncols=2, figsize=(11 * self.factor_width, 5.5 * self.factor_height), constrained_layout=True
405
+ )
251
406
  else:
252
- self.fig, self.ax = plt.subplots(figsize=(9, 5.5), constrained_layout=True)
407
+ self.fig, self.ax = plt.subplots(
408
+ figsize=(9 * self.factor_width, 5.5 * self.factor_height), constrained_layout=True
409
+ )
253
410
  self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
254
411
  self.canvas.get_tk_widget().pack(padx=5, pady=5)
255
412
  # The next two instructions prevent a segmentation fault when pressing "q"
@@ -369,18 +526,66 @@ class ReviewCosmicRay(ImageDisplay):
369
526
  self.npoints = new_npoints
370
527
  self.ndeg_label.config(text=f"Npoints={self.npoints}, Degree={self.degree}")
371
528
 
529
+ def set_maskfill_params(self):
530
+ """Set the maskfill parameters."""
531
+ new_size = simpledialog.askinteger(
532
+ "Set Maskfill Size", "Enter Maskfill Size (odd integer >=1):", initialvalue=self.maskfill_size, minvalue=1
533
+ )
534
+ if new_size is None:
535
+ return
536
+ if new_size % 2 == 0:
537
+ messagebox.showerror("Input Error", "Maskfill size must be an odd integer.")
538
+ return
539
+ new_operator = simpledialog.askstring(
540
+ "Set Maskfill Operator",
541
+ "Enter Maskfill Operator ('median' or 'mean'):",
542
+ initialvalue=self.maskfill_operator,
543
+ )
544
+ if new_operator is None:
545
+ return
546
+ if new_operator not in ["median", "mean"]:
547
+ messagebox.showerror("Input Error", "Maskfill operator must be 'median' or 'mean'.")
548
+ return
549
+ smooth_str = simpledialog.askstring(
550
+ "Set Maskfill Smooth",
551
+ "Enter Maskfill Smooth ('True' or 'False'):",
552
+ initialvalue=str(self.maskfill_smooth),
553
+ )
554
+ if smooth_str is None:
555
+ return
556
+ smooth_str = smooth_str.strip().lower()
557
+ if smooth_str == "true":
558
+ new_smooth = True
559
+ elif smooth_str == "false":
560
+ new_smooth = False
561
+ else:
562
+ messagebox.showerror("Input Error", "Maskfill Smooth must be 'True' or 'False'.")
563
+ return
564
+ verbose_str = simpledialog.askstring(
565
+ "Set Maskfill Verbose",
566
+ "Enter Maskfill Verbose ('True' or 'False'):",
567
+ initialvalue=str(self.maskfill_verbose),
568
+ )
569
+ if verbose_str is None:
570
+ return
571
+ verbose_str = verbose_str.strip().lower()
572
+ if verbose_str == "true":
573
+ new_verbose = True
574
+ elif verbose_str == "false":
575
+ new_verbose = False
576
+ else:
577
+ messagebox.showerror("Input Error", "Maskfill Verbose must be 'True' or 'False'.")
578
+ return
579
+ self.maskfill_size = new_size
580
+ self.maskfill_operator = new_operator
581
+ self.maskfill_smooth = new_smooth
582
+ self.maskfill_verbose = new_verbose
583
+
372
584
  def set_buttons_after_cleaning_cr(self):
373
585
  """Set the state of buttons after cleaning a cosmic ray."""
586
+ self.disable_interpolation_buttons()
374
587
  self.restore_cr_button.config(state=tk.NORMAL)
375
588
  self.remove_crosses_button.config(state=tk.DISABLED)
376
- self.interp_x_button.config(state=tk.DISABLED)
377
- self.interp_y_button.config(state=tk.DISABLED)
378
- self.interp_s_button.config(state=tk.DISABLED)
379
- self.interp_d_button.config(state=tk.DISABLED)
380
- self.interp_m_button.config(state=tk.DISABLED)
381
- self.interp_l_button.config(state=tk.DISABLED)
382
- self.interp_maskfill_button.config(state=tk.DISABLED)
383
- self.interp_aux_button.config(state=tk.DISABLED)
384
589
 
385
590
  def interp_x(self):
386
591
  """Perform x-direction interpolation to clean a cosmic ray."""
@@ -388,6 +593,7 @@ class ReviewCosmicRay(ImageDisplay):
388
593
  messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x interpolation.")
389
594
  return
390
595
  print(f"X-interpolation of cosmic ray {self.cr_index}")
596
+ print(f"Interpolation parameters: Npoints={self.npoints}, Degree={self.degree}")
391
597
  interpolation_performed, xfit_all, yfit_all = interpolation_x(
392
598
  data=self.data,
393
599
  mask_fixed=self.mask_fixed,
@@ -410,6 +616,7 @@ class ReviewCosmicRay(ImageDisplay):
410
616
  messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for y interpolation.")
411
617
  return
412
618
  print(f"Y-interpolation of cosmic ray {self.cr_index}")
619
+ print(f"Interpolation parameters: Npoints={self.npoints}, Degree={self.degree}")
413
620
  interpolation_performed, xfit_all, yfit_all = interpolation_y(
414
621
  data=self.data,
415
622
  mask_fixed=self.mask_fixed,
@@ -435,6 +642,7 @@ class ReviewCosmicRay(ImageDisplay):
435
642
  The interpolation method to use ('surface', 'median' or 'mean').
436
643
  """
437
644
  print(f"{method} interpolation of cosmic ray {self.cr_index}")
645
+ print(f'Interpolation parameters: Npoints={self.npoints}, method="{method}"')
438
646
  interpolation_performed, xfit_all, yfit_all = interpolation_a(
439
647
  data=self.data,
440
648
  mask_fixed=self.mask_fixed,
@@ -468,6 +676,12 @@ class ReviewCosmicRay(ImageDisplay):
468
676
  def use_maskfill(self):
469
677
  """Use maskfill cleaned data to clean a cosmic ray."""
470
678
  print(f"Maskfill interpolation of cosmic ray {self.cr_index}")
679
+ print(
680
+ f"Maskfill parameters: size={self.maskfill_size}, "
681
+ f"operator={self.maskfill_operator}, "
682
+ f"smooth={self.maskfill_smooth}, "
683
+ f"verbose={self.maskfill_verbose}"
684
+ )
471
685
  ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
472
686
  mask = np.zeros(self.data.shape, dtype=bool)
473
687
  for iy, ix in zip(ycr_list, xcr_list):
@@ -475,9 +689,10 @@ class ReviewCosmicRay(ImageDisplay):
475
689
  smoothed_output, _ = maskfill(
476
690
  input_image=self.data,
477
691
  mask=mask,
478
- size=3,
479
- operator="median",
480
- smooth=True,
692
+ size=self.maskfill_size,
693
+ operator=self.maskfill_operator,
694
+ smooth=self.maskfill_smooth,
695
+ verbose=self.maskfill_verbose,
481
696
  )
482
697
  for iy, ix in zip(ycr_list, xcr_list):
483
698
  self.data[iy, ix] = smoothed_output[iy, ix]
@@ -506,35 +721,21 @@ class ReviewCosmicRay(ImageDisplay):
506
721
  for iy, ix in zip(ycr_list, xcr_list):
507
722
  self.cr_labels[iy, ix] = 0
508
723
  print(f"Removed all pixels of cosmic ray {self.cr_index}")
509
- self.remove_crosses_button.config(state=tk.DISABLED)
510
- self.interp_x_button.config(state=tk.DISABLED)
511
- self.interp_y_button.config(state=tk.DISABLED)
512
- self.interp_s_button.config(state=tk.DISABLED)
513
- self.interp_d_button.config(state=tk.DISABLED)
514
- self.interp_m_button.config(state=tk.DISABLED)
515
- self.interp_l_button.config(state=tk.DISABLED)
516
- self.interp_aux_button.config(state=tk.DISABLED)
724
+ self.disable_interpolation_buttons()
517
725
  self.update_display()
518
726
 
519
727
  def restore_cr(self):
520
728
  """Restore all pixels of the current cosmic ray to their original values."""
521
729
  ycr_list, xcr_list = np.where(self.cr_labels == self.cr_index)
730
+ if len(xcr_list) == 0:
731
+ print(f"No pixels to restore for cosmic ray {self.cr_index}")
732
+ return
522
733
  for iy, ix in zip(ycr_list, xcr_list):
523
734
  self.data[iy, ix] = self.data_original[iy, ix]
524
735
  self.mask_fixed[iy, ix] = False
525
- self.interp_x_button.config(state=tk.NORMAL)
526
- self.interp_y_button.config(state=tk.NORMAL)
527
- self.interp_s_button.config(state=tk.NORMAL)
528
- self.interp_d_button.config(state=tk.NORMAL)
529
- self.interp_m_button.config(state=tk.NORMAL)
530
- if self.cleandata_lacosmic is not None:
531
- if self.last_dilation is None or self.last_dilation == 0:
532
- self.interp_l_button.config(state=tk.NORMAL)
533
- self.interp_maskfill_button.config(state=tk.NORMAL)
534
- if self.auxdata is not None:
535
- self.interp_aux_button.config(state=tk.NORMAL)
536
736
  print(f"Restored all pixels of cosmic ray {self.cr_index}")
537
737
  self.num_cr_cleaned -= 1
738
+ self.enable_interpolation_buttons()
538
739
  self.remove_crosses_button.config(state=tk.NORMAL)
539
740
  self.restore_cr_button.config(state=tk.DISABLED)
540
741
  self.update_display()
@@ -550,16 +751,7 @@ class ReviewCosmicRay(ImageDisplay):
550
751
  return # important: do not remove (to avoid errors)
551
752
  self.first_plot = True
552
753
  self.restore_cr_button.config(state=tk.DISABLED)
553
- self.interp_x_button.config(state=tk.NORMAL)
554
- self.interp_y_button.config(state=tk.NORMAL)
555
- self.interp_s_button.config(state=tk.NORMAL)
556
- self.interp_d_button.config(state=tk.NORMAL)
557
- self.interp_m_button.config(state=tk.NORMAL)
558
- if self.cleandata_lacosmic is not None:
559
- if self.last_dilation is None or self.last_dilation == 0:
560
- self.interp_l_button.config(state=tk.NORMAL)
561
- if self.auxdata is not None:
562
- self.interp_aux_button.config(state=tk.NORMAL)
754
+ self.enable_interpolation_buttons()
563
755
  self.remove_crosses_button.config(state=tk.NORMAL)
564
756
  self.update_display()
565
757
 
@@ -624,25 +816,35 @@ class ReviewCosmicRay(ImageDisplay):
624
816
  print(f"Pixel ({ix+1}, {iy+1}), with signal {self.data[iy, ix]}, marked as cosmic ray.")
625
817
  xcr_list, ycr_list = np.where(self.cr_labels == self.cr_index)
626
818
  if len(xcr_list) == 0:
627
- self.interp_x_button.config(state=tk.DISABLED)
628
- self.interp_y_button.config(state=tk.DISABLED)
629
- self.interp_s_button.config(state=tk.DISABLED)
630
- self.interp_d_button.config(state=tk.DISABLED)
631
- self.interp_m_button.config(state=tk.DISABLED)
632
- self.interp_l_button.config(state=tk.DISABLED)
633
- self.interp_aux_button.config(state=tk.DISABLED)
819
+ self.disable_interpolation_buttons()
634
820
  self.remove_crosses_button.config(state=tk.DISABLED)
635
821
  else:
636
- self.interp_x_button.config(state=tk.NORMAL)
637
- self.interp_y_button.config(state=tk.NORMAL)
638
- self.interp_s_button.config(state=tk.NORMAL)
639
- self.interp_d_button.config(state=tk.NORMAL)
640
- self.interp_m_button.config(state=tk.NORMAL)
641
- if self.cleandata_lacosmic is not None:
642
- if self.last_dilation is None or self.last_dilation == 0:
643
- self.interp_l_button.config(state=tk.NORMAL)
644
- if self.auxdata is not None:
645
- self.interp_aux_button.config(state=tk.NORMAL)
822
+ self.enable_interpolation_buttons()
646
823
  self.remove_crosses_button.config(state=tk.NORMAL)
647
824
  # Update the display to reflect the change
648
825
  self.update_display()
826
+
827
+ def disable_interpolation_buttons(self):
828
+ """Disable all interpolation buttons."""
829
+ self.interp_x_button.config(state=tk.DISABLED)
830
+ self.interp_y_button.config(state=tk.DISABLED)
831
+ self.interp_s_button.config(state=tk.DISABLED)
832
+ self.interp_d_button.config(state=tk.DISABLED)
833
+ self.interp_m_button.config(state=tk.DISABLED)
834
+ self.interp_l_button.config(state=tk.DISABLED)
835
+ self.interp_maskfill_button.config(state=tk.DISABLED)
836
+ self.interp_aux_button.config(state=tk.DISABLED)
837
+
838
+ def enable_interpolation_buttons(self):
839
+ """Enable all interpolation buttons."""
840
+ self.interp_x_button.config(state=tk.NORMAL)
841
+ self.interp_y_button.config(state=tk.NORMAL)
842
+ self.interp_s_button.config(state=tk.NORMAL)
843
+ self.interp_d_button.config(state=tk.NORMAL)
844
+ self.interp_m_button.config(state=tk.NORMAL)
845
+ if self.cleandata_lacosmic is not None:
846
+ if self.last_dilation is None or self.last_dilation == 0:
847
+ self.interp_l_button.config(state=tk.NORMAL)
848
+ self.interp_maskfill_button.config(state=tk.NORMAL)
849
+ if self.auxdata is not None:
850
+ self.interp_aux_button.config(state=tk.NORMAL)
@@ -0,0 +1,73 @@
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
+ """Tracked button widget for the cleanest module."""
11
+
12
+ import tkinter as tk
13
+ from rich.console import Console
14
+
15
+ console = Console()
16
+
17
+
18
+ class TrackedTkButton:
19
+ """Class to create tracked Tkinter buttons with help text."""
20
+
21
+ def __init__(self, root):
22
+ """Initialize the TrackedTkButton instance.
23
+
24
+ Initializes an empty list to store button information.
25
+
26
+ Parameters
27
+ ----------
28
+ root : tk.Tk or tk.Frame
29
+ The parent widget where the help information about
30
+ the actions associated to each button will be displayed.
31
+ """
32
+ self.root = root
33
+ self.buttons_info = []
34
+
35
+ def new(self, parent, text, command, help_text, alttext=None, **kwargs):
36
+ """Create a Tkinter button with tracking information.
37
+
38
+ Parameters
39
+ ----------
40
+ parent : tk.Widget
41
+ The parent widget where the button will be placed.
42
+ text : str
43
+ The text to display on the button.
44
+ command : callable
45
+ The function to call when the button is pressed.
46
+ help_text : str
47
+ The help text associated with the button.
48
+ alttext : str, optional
49
+ Alternative text for the button.
50
+ **kwargs : dict
51
+ Additional keyword arguments to pass to the Tkinter Button constructor.
52
+
53
+ Returns
54
+ -------
55
+ button : tk.Button
56
+ The created Tkinter button.
57
+ """
58
+ button = tk.Button(parent, text=text, command=command, **kwargs)
59
+ self.buttons_info.append({"button": button, "text": text, "help_text": help_text, "alttext": alttext})
60
+ return button
61
+
62
+ def show_help(self):
63
+ """Display help information for all tracked buttons."""
64
+ console.rule("[bold red]Button Help Information[/bold red]")
65
+ for info in self.buttons_info:
66
+ if info["alttext"] is not None:
67
+ text = info["alttext"]
68
+ else:
69
+ text = info["text"]
70
+ # replace '[' by '\[' to avoid formatting issues
71
+ text = text.replace("[", "\\[")
72
+ console.print(f"[bold blue]{text}[/bold blue]: {info['help_text']}")
73
+ console.rule()
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.8'
12
+ VERSION = '0.6.0'
13
13
 
14
14
 
15
15
  def main():