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.
@@ -17,7 +17,15 @@ from tkinter import simpledialog
17
17
  import sys
18
18
 
19
19
  from astropy.io import fits
20
- from maskfill import maskfill
20
+
21
+ try:
22
+ from maskfill import maskfill
23
+ except ModuleNotFoundError as e:
24
+ raise ModuleNotFoundError(
25
+ "The 'teareduce.cleanest' module requires the 'ccdproc' and 'maskfill' packages. "
26
+ "Please install teareduce with the 'cleanest' extra dependencies: "
27
+ "`pip install teareduce[cleanest]`."
28
+ ) from e
21
29
  import matplotlib.pyplot as plt
22
30
  from matplotlib.backend_bases import key_press_handler
23
31
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
@@ -27,10 +35,15 @@ import os
27
35
  from pathlib import Path
28
36
  from rich import print
29
37
 
38
+ from .askextension import ask_extension_input_image
30
39
  from .centerchildparent import center_on_parent
31
40
  from .definitions import lacosmic_default_dict
32
41
  from .definitions import DEFAULT_NPOINTS_INTERP
33
42
  from .definitions import DEFAULT_DEGREE_INTERP
43
+ from .definitions import DEFAULT_MASKFILL_SIZE
44
+ from .definitions import DEFAULT_MASKFILL_OPERATOR
45
+ from .definitions import DEFAULT_MASKFILL_SMOOTH
46
+ from .definitions import DEFAULT_MASKFILL_VERBOSE
34
47
  from .definitions import MAX_PIXEL_DISTANCE_TO_CR
35
48
  from .definitions import DEFAULT_TK_WINDOW_SIZE_X
36
49
  from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
@@ -45,12 +58,14 @@ from .interpolationeditor import InterpolationEditor
45
58
  from .imagedisplay import ImageDisplay
46
59
  from .lacosmicpad import lacosmicpad
47
60
  from .mergemasks import merge_peak_tail_masks
61
+ from .modalprogressbar import ModalProgressBar
48
62
  from .parametereditor import ParameterEditor
49
63
  from .reviewcosmicray import ReviewCosmicRay
50
- from .modalprogressbar import ModalProgressBar
64
+ from .trackedbutton import TrackedTkButton
51
65
 
52
66
  from ..imshow import imshow
53
67
  from ..sliceregion import SliceRegion2D
68
+ from ..version import VERSION
54
69
  from ..zscale import zscale
55
70
 
56
71
  import matplotlib
@@ -120,7 +135,7 @@ class CosmicRayCleanerApp(ImageDisplay):
120
135
  Update the overlay of cosmic ray pixels on the image.
121
136
  apply_lacosmic()
122
137
  Apply the L.A.Cosmic algorithm to the data.
123
- examine_detected_cr()
138
+ review_detected_cr()
124
139
  Examine detected cosmic rays.
125
140
  stop_app()
126
141
  Stop the application.
@@ -175,10 +190,26 @@ class CosmicRayCleanerApp(ImageDisplay):
175
190
  last_ymax : int
176
191
  Last used maximum y-coordinate for region selection.
177
192
  From 1 to NAXIS2.
193
+ last_inbkg : str or None
194
+ Last used input background image FITS file.
195
+ last_extnum_inbkg : int or None
196
+ Last used FITS extension number for the input background image.
197
+ last_invar : str or None
198
+ Last used input variance image FITS file.
199
+ last_extnum_invar : int or None
200
+ Last used FITS extension number for the input variance image.
178
201
  last_npoints : int
179
202
  Last used number of points for interpolation.
180
203
  last_degree : int
181
204
  Last used degree for interpolation.
205
+ last_maskfill_size : int
206
+ Last used size parameter for maskfill.
207
+ last_maskfill_operator : str
208
+ Last used operator parameter for maskfill.
209
+ last_maskfill_smooth : bool
210
+ Last used smooth parameter for maskfill.
211
+ last_maskfill_verbose : bool
212
+ Last used verbose parameter for maskfill.
182
213
  cleandata_lacosmic : np.ndarray
183
214
  The cleaned data returned from L.A.Cosmic.
184
215
  cr_labels : np.ndarray
@@ -189,13 +220,13 @@ class CosmicRayCleanerApp(ImageDisplay):
189
220
  Flag to indicate if the review window is active.
190
221
  """
191
222
  self.root = root
192
- # self.root.geometry("800x800+50+0") # This does not work in Fedora
223
+ # self.root.geometry("800x700+50+0") # This does not work in Fedora
193
224
  self.width = width
194
225
  self.height = height
195
226
  self.verbose = verbose
196
227
  self.root.minsize(self.width, self.height)
197
228
  self.root.update_idletasks()
198
- self.root.title("Cosmic Ray Cleaner")
229
+ self.root.title(f"Cosmic Ray Cleaner (TEA version {VERSION})")
199
230
  self.fontfamily = fontfamily
200
231
  self.fontsize = fontsize
201
232
  self.default_font = tkfont.nametofont("TkDefaultFont")
@@ -218,8 +249,18 @@ class CosmicRayCleanerApp(ImageDisplay):
218
249
  self.last_xmax = self.data.shape[1]
219
250
  self.last_ymin = 1
220
251
  self.last_ymax = self.data.shape[0]
252
+ self.last_inbkg = None
253
+ self.last_extnum_inbkg = None
254
+ self.inbkg_data = None
255
+ self.last_invar = None
256
+ self.last_extnum_invar = None
257
+ self.invar_data = None
221
258
  self.last_npoints = DEFAULT_NPOINTS_INTERP
222
259
  self.last_degree = DEFAULT_DEGREE_INTERP
260
+ self.last_maskfill_size = DEFAULT_MASKFILL_SIZE
261
+ self.last_maskfill_operator = DEFAULT_MASKFILL_OPERATOR
262
+ self.last_maskfill_smooth = DEFAULT_MASKFILL_SMOOTH
263
+ self.last_maskfill_verbose = DEFAULT_MASKFILL_VERBOSE
223
264
  self.create_widgets()
224
265
  self.cleandata_lacosmic = None
225
266
  self.cr_labels = None
@@ -228,7 +269,7 @@ class CosmicRayCleanerApp(ImageDisplay):
228
269
 
229
270
  def process_detected_cr(self, dilation):
230
271
  """Process the detected cosmic ray mask.
231
-
272
+
232
273
  Parameters
233
274
  ----------
234
275
  dilation : int
@@ -239,21 +280,15 @@ class CosmicRayCleanerApp(ImageDisplay):
239
280
  num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
240
281
  if dilation > 0:
241
282
  # Dilate the mask by the specified number of pixels
242
- self.mask_crfound = dilatemask(
243
- mask=self.mask_crfound, iterations=dilation, connectivity=1
244
- )
283
+ self.mask_crfound = dilatemask(mask=self.mask_crfound, iterations=dilation, connectivity=1)
245
284
  num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
246
285
  sdum = str(num_cr_pixels_after_dilation)
247
286
  else:
248
287
  sdum = str(num_cr_pixels_before_dilation)
249
- print(
250
- "Number of cosmic ray pixels detected..........: "
251
- f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
252
- )
288
+ print("Number of cosmic ray pixels detected..........: " f"{num_cr_pixels_before_dilation:>{len(sdum)}}")
253
289
  if dilation > 0:
254
290
  print(
255
- f"Number of cosmic ray pixels after dilation....: "
256
- f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
291
+ f"Number of cosmic ray pixels after dilation....: " f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
257
292
  )
258
293
  # Label connected components in the mask; note that by default,
259
294
  # structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
@@ -262,7 +297,7 @@ class CosmicRayCleanerApp(ImageDisplay):
262
297
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
263
298
  print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
264
299
  self.replace_detected_cr_button.config(state=tk.NORMAL)
265
- self.examine_detected_cr_button.config(state=tk.NORMAL)
300
+ self.review_detected_cr_button.config(state=tk.NORMAL)
266
301
  self.update_cr_overlay()
267
302
  self.use_cursor = True
268
303
  self.use_cursor_button.config(text="[c]ursor: ON ")
@@ -271,10 +306,17 @@ class CosmicRayCleanerApp(ImageDisplay):
271
306
  self.cr_labels = None
272
307
  self.num_features = 0
273
308
  self.replace_detected_cr_button.config(state=tk.DISABLED)
274
- self.examine_detected_cr_button.config(state=tk.DISABLED)
309
+ self.review_detected_cr_button.config(state=tk.DISABLED)
275
310
 
276
311
  def load_detected_cr_from_file(self):
277
312
  """Load detected cosmic ray mask from a FITS file."""
313
+ if np.any(self.mask_crfound):
314
+ overwrite = messagebox.askyesno(
315
+ "Overwrite Cosmic Ray Mask",
316
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
317
+ )
318
+ if not overwrite:
319
+ return
278
320
  crmask_file = filedialog.askopenfilename(
279
321
  initialdir=os.getcwd(),
280
322
  title="Select FITS file with cosmic ray mask",
@@ -282,18 +324,7 @@ class CosmicRayCleanerApp(ImageDisplay):
282
324
  )
283
325
  if crmask_file:
284
326
  print(f"Selected input FITS file: {crmask_file}")
285
- extension = simpledialog.askstring(
286
- "Select Extension",
287
- f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
288
- initialvalue=None,
289
- )
290
- try:
291
- extension = int(extension)
292
- except ValueError:
293
- pass # Keep as string
294
- dilation = simpledialog.askinteger(
295
- "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
296
- )
327
+ extension = ask_extension_input_image(crmask_file, self.data.shape)
297
328
  try:
298
329
  with fits.open(crmask_file, mode="readonly") as hdul:
299
330
  if isinstance(extension, int):
@@ -302,6 +333,8 @@ class CosmicRayCleanerApp(ImageDisplay):
302
333
  else:
303
334
  if extension not in hdul:
304
335
  raise KeyError(f"Extension name '{extension}' not found.")
336
+ if hdul[extension].header["BITPIX"] not in [8, 16]:
337
+ raise ValueError("Cosmic ray mask must be of integer type (BITPIX=8 or 16).")
305
338
  mask_crfound_loaded = hdul[extension].data.astype(bool)
306
339
  if mask_crfound_loaded.shape != self.data.shape:
307
340
  print(f"data shape...: {self.data.shape}")
@@ -309,9 +342,14 @@ class CosmicRayCleanerApp(ImageDisplay):
309
342
  raise ValueError("Cosmic ray mask has different shape.")
310
343
  self.mask_crfound = mask_crfound_loaded
311
344
  print(f"Loaded cosmic ray mask from {crmask_file}")
345
+ dilation = simpledialog.askinteger(
346
+ "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
347
+ )
312
348
  self.process_detected_cr(dilation=dilation)
349
+ # self.lacosmic_params["dilation"]["value"] = dilation
350
+ self.cleandata_lacosmic = None # Invalidate previous L.A.Cosmic cleaned data
313
351
  except Exception as e:
314
- print(f"Error loading cosmic ray mask: {e}")
352
+ messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
315
353
 
316
354
  def load_fits_file(self):
317
355
  """Load the FITS file and auxiliary file (if provided).
@@ -371,7 +409,9 @@ class CosmicRayCleanerApp(ImageDisplay):
371
409
  else:
372
410
  if self.extension_auxfile not in hdul_aux:
373
411
  raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
374
- print(f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}")
412
+ print(
413
+ f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}"
414
+ )
375
415
  self.auxdata = hdul_aux[self.extension_auxfile].data
376
416
  if self.auxdata.shape != self.data.shape:
377
417
  print(f"data shape...: {self.data.shape}")
@@ -380,6 +420,43 @@ class CosmicRayCleanerApp(ImageDisplay):
380
420
  except Exception as e:
381
421
  sys.exit(f"Error loading auxiliary FITS file: {e}")
382
422
 
423
+ def load_auxdata_from_file(self):
424
+ """Load auxiliary data from a FITS file."""
425
+ if self.auxfile is not None:
426
+ overwrite = messagebox.askyesno(
427
+ "Overwrite Auxiliary Data",
428
+ f"An auxiliary file is already loaded:\n\n{self.auxfile}\n\n" "Do you want to overwrite it?",
429
+ )
430
+ if not overwrite:
431
+ return
432
+ auxfile = filedialog.askopenfilename(
433
+ initialdir=os.getcwd(),
434
+ title="Select auxiliary FITS file",
435
+ filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
436
+ )
437
+ if auxfile:
438
+ extension = ask_extension_input_image(auxfile, self.data.shape)
439
+ try:
440
+ with fits.open(auxfile, mode="readonly") as hdul:
441
+ if isinstance(extension, int):
442
+ if extension < 0 or extension >= len(hdul):
443
+ raise IndexError(f"Extension index {extension} out of range.")
444
+ else:
445
+ if extension not in hdul:
446
+ raise KeyError(f"Extension name '{extension}' not found.")
447
+ auxdata_loaded = hdul[extension].data
448
+ if auxdata_loaded.shape != self.data.shape:
449
+ print(f"data shape...: {self.data.shape}")
450
+ print(f"auxdata shape: {auxdata_loaded.shape}")
451
+ raise ValueError("Auxiliary file has different shape.")
452
+ self.auxfile = auxfile
453
+ self.auxdata = auxdata_loaded
454
+ self.extension_auxfile = extension
455
+ print(f"Loaded auxiliary data from {auxfile}")
456
+ self.toggle_auxdata_button.config(state=tk.NORMAL)
457
+ except Exception as e:
458
+ print(f"Error loading auxiliary FITS file: {e}")
459
+
383
460
  def save_fits_file(self):
384
461
  """Save the cleaned FITS file.
385
462
 
@@ -443,74 +520,153 @@ class CosmicRayCleanerApp(ImageDisplay):
443
520
  and canvas for image display, along with the toolbar for navigation.
444
521
  The relevant attributes are stored in the instance for later use.
445
522
  """
523
+ # Define instance of TrackedTkButton, that facilitates to show help information
524
+ # for each button displayed in the current application window.
525
+ tkbutton = TrackedTkButton(self.root)
526
+
446
527
  # Row 1 of buttons
447
528
  self.button_frame1 = tk.Frame(self.root)
448
529
  self.button_frame1.pack(pady=5)
449
- self.run_lacosmic_button = tk.Button(self.button_frame1, text="Run L.A.Cosmic", command=self.run_lacosmic)
530
+ self.run_lacosmic_button = tkbutton.new(
531
+ self.button_frame1,
532
+ text="Run L.A.Cosmic",
533
+ command=self.run_lacosmic,
534
+ help_text="Run the L.A.Cosmic algorithm to detect cosmic rays in the image.",
535
+ )
450
536
  self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
451
- self.load_detected_cr_button = tk.Button(
452
- self.button_frame1, text="Load detected CRs", command=self.load_detected_cr_from_file
537
+ self.load_auxdata_button = tkbutton.new(
538
+ self.button_frame1,
539
+ text="Load auxdata",
540
+ command=self.load_auxdata_from_file,
541
+ help_text="Load an auxiliary FITS file for display.",
542
+ )
543
+ self.load_auxdata_button.pack(side=tk.LEFT, padx=5)
544
+ self.load_detected_cr_button = tkbutton.new(
545
+ self.button_frame1,
546
+ text="Load CR mask",
547
+ command=self.load_detected_cr_from_file,
548
+ help_text="Load a previously saved cosmic ray mask from a FITS file.",
453
549
  )
454
550
  self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
455
- self.replace_detected_cr_button = tk.Button(
456
- self.button_frame1, text="Replace detected CRs", command=self.apply_lacosmic
551
+ self.replace_detected_cr_button = tkbutton.new(
552
+ self.button_frame1,
553
+ text="Replace detected CRs",
554
+ command=self.apply_lacosmic,
555
+ help_text="Apply the cleaning to the detected cosmic rays.",
457
556
  )
458
557
  self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
459
558
  self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
460
- self.examine_detected_cr_button = tk.Button(
461
- self.button_frame1, text="Examine detected CRs", command=lambda: self.examine_detected_cr(1)
559
+ self.review_detected_cr_button = tkbutton.new(
560
+ self.button_frame1,
561
+ text="Review detected CRs",
562
+ command=lambda: self.review_detected_cr(1),
563
+ help_text="Review the detected cosmic rays.",
462
564
  )
463
- self.examine_detected_cr_button.pack(side=tk.LEFT, padx=5)
464
- self.examine_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
565
+ self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
566
+ self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
465
567
 
466
568
  # Row 2 of buttons
467
569
  self.button_frame2 = tk.Frame(self.root)
468
570
  self.button_frame2.pack(pady=5)
469
- self.toggle_auxdata_button = tk.Button(self.button_frame2, text="[t]oggle data", command=self.toggle_auxdata)
571
+ self.use_cursor = False
572
+ self.use_cursor_button = tkbutton.new(
573
+ self.button_frame2,
574
+ text="[c]ursor: OFF",
575
+ command=self.set_cursor_onoff,
576
+ help_text="Toggle the cursor ON or OFF (to select CR with mouse).",
577
+ alttext="[c]ursor: ??",
578
+ )
579
+ self.use_cursor_button.pack(side=tk.LEFT, padx=5)
580
+ self.toggle_auxdata_button = tkbutton.new(
581
+ self.button_frame2,
582
+ text="[t]oggle data",
583
+ command=self.toggle_auxdata,
584
+ help_text="Toggle the display of auxiliary data.",
585
+ )
470
586
  self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
471
587
  if self.auxdata is None:
472
588
  self.toggle_auxdata_button.config(state=tk.DISABLED)
473
589
  else:
474
590
  self.toggle_auxdata_button.config(state=tk.NORMAL)
475
591
  self.image_aspect = "equal"
476
- self.toggle_aspect_button = tk.Button(
477
- self.button_frame2, text=f"[a]spect: {self.image_aspect}", command=self.toggle_aspect
592
+ self.toggle_aspect_button = tkbutton.new(
593
+ self.button_frame2,
594
+ text=f"[a]spect: {self.image_aspect}",
595
+ command=self.toggle_aspect,
596
+ help_text="Toggle the image aspect ratio.",
478
597
  )
479
598
  self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
480
- self.save_button = tk.Button(self.button_frame2, text="Save cleaned FITS", command=self.save_fits_file)
599
+ self.save_button = tkbutton.new(
600
+ self.button_frame2,
601
+ text="Save cleaned FITS",
602
+ command=self.save_fits_file,
603
+ help_text="Save the cleaned FITS file.",
604
+ )
481
605
  self.save_button.pack(side=tk.LEFT, padx=5)
482
606
  self.save_button.config(state=tk.DISABLED) # Initially disabled
483
- self.stop_button = tk.Button(self.button_frame2, text="Stop program", command=self.stop_app)
607
+ self.stop_button = tkbutton.new(
608
+ self.button_frame2, text="Stop program", command=self.stop_app, help_text="Stop the application."
609
+ )
484
610
  self.stop_button.pack(side=tk.LEFT, padx=5)
485
611
 
486
612
  # Row 3 of buttons
487
613
  self.button_frame3 = tk.Frame(self.root)
488
614
  self.button_frame3.pack(pady=5)
489
- self.use_cursor = False
490
- self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
491
- self.use_cursor_button.pack(side=tk.LEFT, padx=5)
492
615
  vmin, vmax = zscale(self.data)
493
- self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
616
+ self.vmin_button = tkbutton.new(
617
+ self.button_frame3,
618
+ text=f"vmin: {vmin:.2f}",
619
+ command=self.set_vmin,
620
+ help_text="Set the minimum value for the display scale.",
621
+ alttext="vmin: ??",
622
+ )
494
623
  self.vmin_button.pack(side=tk.LEFT, padx=5)
495
- self.vmax_button = tk.Button(self.button_frame3, text=f"vmax: {vmax:.2f}", command=self.set_vmax)
624
+ self.vmax_button = tkbutton.new(
625
+ self.button_frame3,
626
+ text=f"vmax: {vmax:.2f}",
627
+ command=self.set_vmax,
628
+ help_text="Set the maximum value for the display scale.",
629
+ alttext="vmax: ??",
630
+ )
496
631
  self.vmax_button.pack(side=tk.LEFT, padx=5)
497
- self.set_minmax_button = tk.Button(self.button_frame3, text="minmax [,]", command=self.set_minmax)
632
+ self.set_minmax_button = tkbutton.new(
633
+ self.button_frame3,
634
+ text="minmax [,]",
635
+ command=self.set_minmax,
636
+ help_text="Set the minimum and maximum values for the display scale.",
637
+ )
498
638
  self.set_minmax_button.pack(side=tk.LEFT, padx=5)
499
- self.set_zscale_button = tk.Button(self.button_frame3, text="zscale [/]", command=self.set_zscale)
639
+ self.set_zscale_button = tkbutton.new(
640
+ self.button_frame3,
641
+ text="zscale [/]",
642
+ command=self.set_zscale,
643
+ help_text="Set the display scale using zscale.",
644
+ )
500
645
  self.set_zscale_button.pack(side=tk.LEFT, padx=5)
501
646
  if self.overplot_cr_pixels:
502
- self.overplot_cr_button = tk.Button(
647
+ self.overplot_cr_button = tkbutton.new(
503
648
  self.button_frame3,
504
649
  text="CR overlay: ON ",
505
650
  command=self.toggle_cr_overlay,
651
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
652
+ alttext="CR overlay: ??",
506
653
  )
507
654
  else:
508
- self.overplot_cr_button = tk.Button(
655
+ self.overplot_cr_button = tkbutton.new(
509
656
  self.button_frame3,
510
657
  text="CR overlay: OFF",
511
658
  command=self.toggle_cr_overlay,
659
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
660
+ alttext="CR overlay: ??",
512
661
  )
513
662
  self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
663
+ self.help_button = tkbutton.new(
664
+ self.button_frame3,
665
+ text="Help",
666
+ command=tkbutton.show_help,
667
+ help_text="Show help information for all buttons.",
668
+ )
669
+ self.help_button.pack(side=tk.LEFT, padx=5)
514
670
 
515
671
  # Figure
516
672
  self.plot_frame = tk.Frame(self.root)
@@ -599,6 +755,13 @@ class CosmicRayCleanerApp(ImageDisplay):
599
755
 
600
756
  def run_lacosmic(self):
601
757
  """Run L.A.Cosmic to detect cosmic rays."""
758
+ if np.any(self.mask_crfound):
759
+ overwrite = messagebox.askyesno(
760
+ "Overwrite Cosmic Ray Mask",
761
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
762
+ )
763
+ if not overwrite:
764
+ return
602
765
  self.run_lacosmic_button.config(state=tk.DISABLED)
603
766
  # Define parameters for L.A.Cosmic from default dictionary
604
767
  editor_window = tk.Toplevel(self.root)
@@ -612,6 +775,10 @@ class CosmicRayCleanerApp(ImageDisplay):
612
775
  ymin=self.last_ymin,
613
776
  ymax=self.last_ymax,
614
777
  imgshape=self.data.shape,
778
+ inbkg=self.last_inbkg,
779
+ extnum_inbkg=self.last_extnum_inbkg,
780
+ invar=self.last_invar,
781
+ extnum_invar=self.last_extnum_invar,
615
782
  )
616
783
  # Make it modal (blocks interaction with main window)
617
784
  editor_window.transient(self.root)
@@ -626,6 +793,24 @@ class CosmicRayCleanerApp(ImageDisplay):
626
793
  self.last_xmax = updated_params["xmax"]["value"]
627
794
  self.last_ymin = updated_params["ymin"]["value"]
628
795
  self.last_ymax = updated_params["ymax"]["value"]
796
+ self.last_inbkg = updated_params["inbkg"]["value"]
797
+ self.last_extnum_inbkg = updated_params["extnum_inbkg"]["value"]
798
+ self.last_invar = updated_params["invar"]["value"]
799
+ self.last_extnum_invar = updated_params["extnum_invar"]["value"]
800
+ if self.last_inbkg is not None:
801
+ with fits.open(self.last_inbkg, mode="readonly") as hdul_inbkg:
802
+ if self.last_extnum_inbkg < 0 or self.last_extnum_inbkg >= len(hdul_inbkg):
803
+ raise IndexError(f"Extension index {self.last_extnum_inbkg} out of range.")
804
+ self.inbkg_data = hdul_inbkg[self.last_extnum_inbkg].data.astype(np.float32)
805
+ else:
806
+ self.inbkg_data = None
807
+ if self.last_invar is not None:
808
+ with fits.open(self.last_invar, mode="readonly") as hdul_invar:
809
+ if self.last_extnum_invar < 0 or self.last_extnum_invar >= len(hdul_invar):
810
+ raise IndexError(f"Extension index {self.last_extnum_invar} out of range.")
811
+ self.invar_data = hdul_invar[self.last_extnum_invar].data.astype(np.float32)
812
+ else:
813
+ self.invar_data = None
629
814
  usefulregion = SliceRegion2D(
630
815
  f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
631
816
  ).python
@@ -644,14 +829,27 @@ class CosmicRayCleanerApp(ImageDisplay):
644
829
  borderpadd = updated_params["borderpadd"]["value"]
645
830
  cleandata_lacosmic, mask_crfound = lacosmicpad(
646
831
  pad_width=borderpadd,
832
+ show_arguments=self.verbose,
647
833
  ccd=self.data,
648
- gain=self.lacosmic_params["run1_gain"]["value"],
649
- readnoise=self.lacosmic_params["run1_readnoise"]["value"],
834
+ gain_apply=True, # Always apply gain
650
835
  sigclip=self.lacosmic_params["run1_sigclip"]["value"],
651
836
  sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
652
837
  objlim=self.lacosmic_params["run1_objlim"]["value"],
838
+ gain=self.lacosmic_params["run1_gain"]["value"],
839
+ readnoise=self.lacosmic_params["run1_readnoise"]["value"],
840
+ satlevel=self.lacosmic_params["run1_satlevel"]["value"],
653
841
  niter=self.lacosmic_params["run1_niter"]["value"],
842
+ sepmed=self.lacosmic_params["run1_sepmed"]["value"],
843
+ cleantype=self.lacosmic_params["run1_cleantype"]["value"],
844
+ fsmode=self.lacosmic_params["run1_fsmode"]["value"],
845
+ psfmodel=self.lacosmic_params["run1_psfmodel"]["value"],
846
+ psffwhm_x=self.lacosmic_params["run1_psffwhm_x"]["value"],
847
+ psffwhm_y=self.lacosmic_params["run1_psffwhm_y"]["value"],
848
+ psfsize=self.lacosmic_params["run1_psfsize"]["value"],
849
+ psfbeta=self.lacosmic_params["run1_psfbeta"]["value"],
654
850
  verbose=self.lacosmic_params["run1_verbose"]["value"],
851
+ inbkg=self.inbkg_data,
852
+ invar=self.invar_data,
655
853
  )
656
854
  # Apply usefulmask to consider only selected region
657
855
  cleandata_lacosmic *= usefulmask
@@ -661,14 +859,27 @@ class CosmicRayCleanerApp(ImageDisplay):
661
859
  print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
662
860
  cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
663
861
  pad_width=borderpadd,
862
+ show_arguments=self.verbose,
664
863
  ccd=self.data,
665
- gain=self.lacosmic_params["run2_gain"]["value"],
666
- readnoise=self.lacosmic_params["run2_readnoise"]["value"],
864
+ gain_apply=True, # Always apply gain
667
865
  sigclip=self.lacosmic_params["run2_sigclip"]["value"],
668
866
  sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
669
867
  objlim=self.lacosmic_params["run2_objlim"]["value"],
868
+ gain=self.lacosmic_params["run2_gain"]["value"],
869
+ readnoise=self.lacosmic_params["run2_readnoise"]["value"],
870
+ satlevel=self.lacosmic_params["run2_satlevel"]["value"],
670
871
  niter=self.lacosmic_params["run2_niter"]["value"],
872
+ sepmed=self.lacosmic_params["run2_sepmed"]["value"],
873
+ cleantype=self.lacosmic_params["run2_cleantype"]["value"],
874
+ fsmode=self.lacosmic_params["run2_fsmode"]["value"],
875
+ psfmodel=self.lacosmic_params["run2_psfmodel"]["value"],
876
+ psffwhm_x=self.lacosmic_params["run2_psffwhm_x"]["value"],
877
+ psffwhm_y=self.lacosmic_params["run2_psffwhm_y"]["value"],
878
+ psfsize=self.lacosmic_params["run2_psfsize"]["value"],
879
+ psfbeta=self.lacosmic_params["run2_psfbeta"]["value"],
671
880
  verbose=self.lacosmic_params["run2_verbose"]["value"],
881
+ inbkg=self.inbkg_data,
882
+ invar=self.invar_data,
672
883
  )
673
884
  # Apply usefulmask to consider only selected region
674
885
  cleandata_lacosmic2 *= usefulmask
@@ -736,7 +947,12 @@ class CosmicRayCleanerApp(ImageDisplay):
736
947
  last_dilation=self.lacosmic_params["dilation"]["value"],
737
948
  last_npoints=self.last_npoints,
738
949
  last_degree=self.last_degree,
950
+ last_maskfill_size=self.last_maskfill_size,
951
+ last_maskfill_operator=self.last_maskfill_operator,
952
+ last_maskfill_smooth=self.last_maskfill_smooth,
953
+ last_maskfill_verbose=self.last_maskfill_verbose,
739
954
  auxdata=self.auxdata,
955
+ cleandata_lacosmic=self.cleandata_lacosmic,
740
956
  xmin=self.last_xmin,
741
957
  xmax=self.last_xmax,
742
958
  ymin=self.last_ymin,
@@ -753,8 +969,13 @@ class CosmicRayCleanerApp(ImageDisplay):
753
969
  if cleaning_method is None:
754
970
  print("Interpolation method selection cancelled. No cleaning applied!")
755
971
  return
972
+ # Update last employed parameters
756
973
  self.last_npoints = editor.npoints
757
974
  self.last_degree = editor.degree
975
+ self.last_maskfill_size = editor.maskfill_size
976
+ self.last_maskfill_operator = editor.maskfill_operator
977
+ self.last_maskfill_smooth = editor.maskfill_smooth
978
+ self.last_maskfill_verbose = editor.maskfill_verbose
758
979
  cleaning_region = SliceRegion2D(
759
980
  f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
760
981
  ).python
@@ -776,12 +997,19 @@ class CosmicRayCleanerApp(ImageDisplay):
776
997
  data_has_been_modified = True
777
998
  elif cleaning_method == "maskfill":
778
999
  # Replace detected CR pixels with local median values
1000
+ print(
1001
+ f"Maskfill parameters: size={self.last_maskfill_size}, "
1002
+ f"operator={self.last_maskfill_operator}, "
1003
+ f"smooth={self.last_maskfill_smooth}, "
1004
+ f"verbose={self.last_maskfill_verbose}"
1005
+ )
779
1006
  smoothed_output, _ = maskfill(
780
1007
  input_image=self.data,
781
1008
  mask=mask_crfound_region,
782
- size=3,
783
- operator="median",
784
- smooth=True,
1009
+ size=self.last_maskfill_size,
1010
+ operator=self.last_maskfill_operator,
1011
+ smooth=self.last_maskfill_smooth,
1012
+ verbose=self.last_maskfill_verbose,
785
1013
  )
786
1014
  self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
787
1015
  # update mask_fixed to include the newly fixed pixels
@@ -884,11 +1112,11 @@ class CosmicRayCleanerApp(ImageDisplay):
884
1112
  if data_has_been_modified:
885
1113
  self.save_button.config(state=tk.NORMAL)
886
1114
  if self.num_features == 0:
887
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1115
+ self.review_detected_cr_button.config(state=tk.DISABLED)
888
1116
  self.replace_detected_cr_button.config(state=tk.DISABLED)
889
1117
  self.update_cr_overlay()
890
1118
 
891
- def examine_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
1119
+ def review_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
892
1120
  """Open a window to examine and possibly clean detected cosmic rays."""
893
1121
  self.working_in_review_window = True
894
1122
  review_window = tk.Toplevel(self.root)
@@ -899,6 +1127,8 @@ class CosmicRayCleanerApp(ImageDisplay):
899
1127
  tmp_cr_labels[iypix - 1, ixpix - 1] = 1
900
1128
  review = ReviewCosmicRay(
901
1129
  root=review_window,
1130
+ root_width=self.width,
1131
+ root_height=self.height,
902
1132
  data=self.data,
903
1133
  auxdata=self.auxdata,
904
1134
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -913,6 +1143,8 @@ class CosmicRayCleanerApp(ImageDisplay):
913
1143
  else:
914
1144
  review = ReviewCosmicRay(
915
1145
  root=review_window,
1146
+ root_width=self.width,
1147
+ root_height=self.height,
916
1148
  data=self.data,
917
1149
  auxdata=self.auxdata,
918
1150
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -954,7 +1186,7 @@ class CosmicRayCleanerApp(ImageDisplay):
954
1186
  if review.num_cr_cleaned > 0:
955
1187
  self.save_button.config(state=tk.NORMAL)
956
1188
  if self.num_features == 0:
957
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1189
+ self.review_detected_cr_button.config(state=tk.DISABLED)
958
1190
  self.replace_detected_cr_button.config(state=tk.DISABLED)
959
1191
  self.update_cr_overlay()
960
1192
 
@@ -1062,4 +1294,4 @@ class CosmicRayCleanerApp(ImageDisplay):
1062
1294
  else:
1063
1295
  ixpix = None
1064
1296
  iypix = None
1065
- self.examine_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)
1297
+ self.review_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)