teareduce 0.5.9__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,6 +17,7 @@ from tkinter import simpledialog
17
17
  import sys
18
18
 
19
19
  from astropy.io import fits
20
+
20
21
  try:
21
22
  from maskfill import maskfill
22
23
  except ModuleNotFoundError as e:
@@ -34,10 +35,15 @@ import os
34
35
  from pathlib import Path
35
36
  from rich import print
36
37
 
38
+ from .askextension import ask_extension_input_image
37
39
  from .centerchildparent import center_on_parent
38
40
  from .definitions import lacosmic_default_dict
39
41
  from .definitions import DEFAULT_NPOINTS_INTERP
40
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
41
47
  from .definitions import MAX_PIXEL_DISTANCE_TO_CR
42
48
  from .definitions import DEFAULT_TK_WINDOW_SIZE_X
43
49
  from .definitions import DEFAULT_TK_WINDOW_SIZE_Y
@@ -52,12 +58,14 @@ from .interpolationeditor import InterpolationEditor
52
58
  from .imagedisplay import ImageDisplay
53
59
  from .lacosmicpad import lacosmicpad
54
60
  from .mergemasks import merge_peak_tail_masks
61
+ from .modalprogressbar import ModalProgressBar
55
62
  from .parametereditor import ParameterEditor
56
63
  from .reviewcosmicray import ReviewCosmicRay
57
- from .modalprogressbar import ModalProgressBar
64
+ from .trackedbutton import TrackedTkButton
58
65
 
59
66
  from ..imshow import imshow
60
67
  from ..sliceregion import SliceRegion2D
68
+ from ..version import VERSION
61
69
  from ..zscale import zscale
62
70
 
63
71
  import matplotlib
@@ -127,7 +135,7 @@ class CosmicRayCleanerApp(ImageDisplay):
127
135
  Update the overlay of cosmic ray pixels on the image.
128
136
  apply_lacosmic()
129
137
  Apply the L.A.Cosmic algorithm to the data.
130
- examine_detected_cr()
138
+ review_detected_cr()
131
139
  Examine detected cosmic rays.
132
140
  stop_app()
133
141
  Stop the application.
@@ -182,10 +190,26 @@ class CosmicRayCleanerApp(ImageDisplay):
182
190
  last_ymax : int
183
191
  Last used maximum y-coordinate for region selection.
184
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.
185
201
  last_npoints : int
186
202
  Last used number of points for interpolation.
187
203
  last_degree : int
188
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.
189
213
  cleandata_lacosmic : np.ndarray
190
214
  The cleaned data returned from L.A.Cosmic.
191
215
  cr_labels : np.ndarray
@@ -196,13 +220,13 @@ class CosmicRayCleanerApp(ImageDisplay):
196
220
  Flag to indicate if the review window is active.
197
221
  """
198
222
  self.root = root
199
- # 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
200
224
  self.width = width
201
225
  self.height = height
202
226
  self.verbose = verbose
203
227
  self.root.minsize(self.width, self.height)
204
228
  self.root.update_idletasks()
205
- self.root.title("Cosmic Ray Cleaner")
229
+ self.root.title(f"Cosmic Ray Cleaner (TEA version {VERSION})")
206
230
  self.fontfamily = fontfamily
207
231
  self.fontsize = fontsize
208
232
  self.default_font = tkfont.nametofont("TkDefaultFont")
@@ -225,8 +249,18 @@ class CosmicRayCleanerApp(ImageDisplay):
225
249
  self.last_xmax = self.data.shape[1]
226
250
  self.last_ymin = 1
227
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
228
258
  self.last_npoints = DEFAULT_NPOINTS_INTERP
229
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
230
264
  self.create_widgets()
231
265
  self.cleandata_lacosmic = None
232
266
  self.cr_labels = None
@@ -235,7 +269,7 @@ class CosmicRayCleanerApp(ImageDisplay):
235
269
 
236
270
  def process_detected_cr(self, dilation):
237
271
  """Process the detected cosmic ray mask.
238
-
272
+
239
273
  Parameters
240
274
  ----------
241
275
  dilation : int
@@ -246,21 +280,15 @@ class CosmicRayCleanerApp(ImageDisplay):
246
280
  num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
247
281
  if dilation > 0:
248
282
  # Dilate the mask by the specified number of pixels
249
- self.mask_crfound = dilatemask(
250
- mask=self.mask_crfound, iterations=dilation, connectivity=1
251
- )
283
+ self.mask_crfound = dilatemask(mask=self.mask_crfound, iterations=dilation, connectivity=1)
252
284
  num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
253
285
  sdum = str(num_cr_pixels_after_dilation)
254
286
  else:
255
287
  sdum = str(num_cr_pixels_before_dilation)
256
- print(
257
- "Number of cosmic ray pixels detected..........: "
258
- f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
259
- )
288
+ print("Number of cosmic ray pixels detected..........: " f"{num_cr_pixels_before_dilation:>{len(sdum)}}")
260
289
  if dilation > 0:
261
290
  print(
262
- f"Number of cosmic ray pixels after dilation....: "
263
- 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)}}"
264
292
  )
265
293
  # Label connected components in the mask; note that by default,
266
294
  # structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
@@ -269,7 +297,7 @@ class CosmicRayCleanerApp(ImageDisplay):
269
297
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
270
298
  print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
271
299
  self.replace_detected_cr_button.config(state=tk.NORMAL)
272
- self.examine_detected_cr_button.config(state=tk.NORMAL)
300
+ self.review_detected_cr_button.config(state=tk.NORMAL)
273
301
  self.update_cr_overlay()
274
302
  self.use_cursor = True
275
303
  self.use_cursor_button.config(text="[c]ursor: ON ")
@@ -278,10 +306,17 @@ class CosmicRayCleanerApp(ImageDisplay):
278
306
  self.cr_labels = None
279
307
  self.num_features = 0
280
308
  self.replace_detected_cr_button.config(state=tk.DISABLED)
281
- self.examine_detected_cr_button.config(state=tk.DISABLED)
309
+ self.review_detected_cr_button.config(state=tk.DISABLED)
282
310
 
283
311
  def load_detected_cr_from_file(self):
284
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
285
320
  crmask_file = filedialog.askopenfilename(
286
321
  initialdir=os.getcwd(),
287
322
  title="Select FITS file with cosmic ray mask",
@@ -289,18 +324,7 @@ class CosmicRayCleanerApp(ImageDisplay):
289
324
  )
290
325
  if crmask_file:
291
326
  print(f"Selected input FITS file: {crmask_file}")
292
- extension = simpledialog.askstring(
293
- "Select Extension",
294
- f"\nEnter extension number or name for file:\n{Path(crmask_file).name}",
295
- initialvalue=None,
296
- )
297
- try:
298
- extension = int(extension)
299
- except ValueError:
300
- pass # Keep as string
301
- dilation = simpledialog.askinteger(
302
- "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
303
- )
327
+ extension = ask_extension_input_image(crmask_file, self.data.shape)
304
328
  try:
305
329
  with fits.open(crmask_file, mode="readonly") as hdul:
306
330
  if isinstance(extension, int):
@@ -309,6 +333,8 @@ class CosmicRayCleanerApp(ImageDisplay):
309
333
  else:
310
334
  if extension not in hdul:
311
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).")
312
338
  mask_crfound_loaded = hdul[extension].data.astype(bool)
313
339
  if mask_crfound_loaded.shape != self.data.shape:
314
340
  print(f"data shape...: {self.data.shape}")
@@ -316,9 +342,14 @@ class CosmicRayCleanerApp(ImageDisplay):
316
342
  raise ValueError("Cosmic ray mask has different shape.")
317
343
  self.mask_crfound = mask_crfound_loaded
318
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
+ )
319
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
320
351
  except Exception as e:
321
- print(f"Error loading cosmic ray mask: {e}")
352
+ messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
322
353
 
323
354
  def load_fits_file(self):
324
355
  """Load the FITS file and auxiliary file (if provided).
@@ -378,7 +409,9 @@ class CosmicRayCleanerApp(ImageDisplay):
378
409
  else:
379
410
  if self.extension_auxfile not in hdul_aux:
380
411
  raise KeyError(f"Extension name '{self.extension_auxfile}' not found.")
381
- 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
+ )
382
415
  self.auxdata = hdul_aux[self.extension_auxfile].data
383
416
  if self.auxdata.shape != self.data.shape:
384
417
  print(f"data shape...: {self.data.shape}")
@@ -387,6 +420,43 @@ class CosmicRayCleanerApp(ImageDisplay):
387
420
  except Exception as e:
388
421
  sys.exit(f"Error loading auxiliary FITS file: {e}")
389
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
+
390
460
  def save_fits_file(self):
391
461
  """Save the cleaned FITS file.
392
462
 
@@ -450,74 +520,153 @@ class CosmicRayCleanerApp(ImageDisplay):
450
520
  and canvas for image display, along with the toolbar for navigation.
451
521
  The relevant attributes are stored in the instance for later use.
452
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
+
453
527
  # Row 1 of buttons
454
528
  self.button_frame1 = tk.Frame(self.root)
455
529
  self.button_frame1.pack(pady=5)
456
- 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
+ )
457
536
  self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
458
- self.load_detected_cr_button = tk.Button(
459
- 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.",
460
549
  )
461
550
  self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
462
- self.replace_detected_cr_button = tk.Button(
463
- 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.",
464
556
  )
465
557
  self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
466
558
  self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
467
- self.examine_detected_cr_button = tk.Button(
468
- 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.",
469
564
  )
470
- self.examine_detected_cr_button.pack(side=tk.LEFT, padx=5)
471
- 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
472
567
 
473
568
  # Row 2 of buttons
474
569
  self.button_frame2 = tk.Frame(self.root)
475
570
  self.button_frame2.pack(pady=5)
476
- 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
+ )
477
586
  self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
478
587
  if self.auxdata is None:
479
588
  self.toggle_auxdata_button.config(state=tk.DISABLED)
480
589
  else:
481
590
  self.toggle_auxdata_button.config(state=tk.NORMAL)
482
591
  self.image_aspect = "equal"
483
- self.toggle_aspect_button = tk.Button(
484
- 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.",
485
597
  )
486
598
  self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
487
- 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
+ )
488
605
  self.save_button.pack(side=tk.LEFT, padx=5)
489
606
  self.save_button.config(state=tk.DISABLED) # Initially disabled
490
- 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
+ )
491
610
  self.stop_button.pack(side=tk.LEFT, padx=5)
492
611
 
493
612
  # Row 3 of buttons
494
613
  self.button_frame3 = tk.Frame(self.root)
495
614
  self.button_frame3.pack(pady=5)
496
- self.use_cursor = False
497
- self.use_cursor_button = tk.Button(self.button_frame3, text="[c]ursor: OFF", command=self.set_cursor_onoff)
498
- self.use_cursor_button.pack(side=tk.LEFT, padx=5)
499
615
  vmin, vmax = zscale(self.data)
500
- 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
+ )
501
623
  self.vmin_button.pack(side=tk.LEFT, padx=5)
502
- 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
+ )
503
631
  self.vmax_button.pack(side=tk.LEFT, padx=5)
504
- 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
+ )
505
638
  self.set_minmax_button.pack(side=tk.LEFT, padx=5)
506
- 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
+ )
507
645
  self.set_zscale_button.pack(side=tk.LEFT, padx=5)
508
646
  if self.overplot_cr_pixels:
509
- self.overplot_cr_button = tk.Button(
647
+ self.overplot_cr_button = tkbutton.new(
510
648
  self.button_frame3,
511
649
  text="CR overlay: ON ",
512
650
  command=self.toggle_cr_overlay,
651
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
652
+ alttext="CR overlay: ??",
513
653
  )
514
654
  else:
515
- self.overplot_cr_button = tk.Button(
655
+ self.overplot_cr_button = tkbutton.new(
516
656
  self.button_frame3,
517
657
  text="CR overlay: OFF",
518
658
  command=self.toggle_cr_overlay,
659
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
660
+ alttext="CR overlay: ??",
519
661
  )
520
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)
521
670
 
522
671
  # Figure
523
672
  self.plot_frame = tk.Frame(self.root)
@@ -606,6 +755,13 @@ class CosmicRayCleanerApp(ImageDisplay):
606
755
 
607
756
  def run_lacosmic(self):
608
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
609
765
  self.run_lacosmic_button.config(state=tk.DISABLED)
610
766
  # Define parameters for L.A.Cosmic from default dictionary
611
767
  editor_window = tk.Toplevel(self.root)
@@ -619,6 +775,10 @@ class CosmicRayCleanerApp(ImageDisplay):
619
775
  ymin=self.last_ymin,
620
776
  ymax=self.last_ymax,
621
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,
622
782
  )
623
783
  # Make it modal (blocks interaction with main window)
624
784
  editor_window.transient(self.root)
@@ -633,6 +793,24 @@ class CosmicRayCleanerApp(ImageDisplay):
633
793
  self.last_xmax = updated_params["xmax"]["value"]
634
794
  self.last_ymin = updated_params["ymin"]["value"]
635
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
636
814
  usefulregion = SliceRegion2D(
637
815
  f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
638
816
  ).python
@@ -651,14 +829,27 @@ class CosmicRayCleanerApp(ImageDisplay):
651
829
  borderpadd = updated_params["borderpadd"]["value"]
652
830
  cleandata_lacosmic, mask_crfound = lacosmicpad(
653
831
  pad_width=borderpadd,
832
+ show_arguments=self.verbose,
654
833
  ccd=self.data,
655
- gain=self.lacosmic_params["run1_gain"]["value"],
656
- readnoise=self.lacosmic_params["run1_readnoise"]["value"],
834
+ gain_apply=True, # Always apply gain
657
835
  sigclip=self.lacosmic_params["run1_sigclip"]["value"],
658
836
  sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
659
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"],
660
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"],
661
850
  verbose=self.lacosmic_params["run1_verbose"]["value"],
851
+ inbkg=self.inbkg_data,
852
+ invar=self.invar_data,
662
853
  )
663
854
  # Apply usefulmask to consider only selected region
664
855
  cleandata_lacosmic *= usefulmask
@@ -668,14 +859,27 @@ class CosmicRayCleanerApp(ImageDisplay):
668
859
  print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
669
860
  cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
670
861
  pad_width=borderpadd,
862
+ show_arguments=self.verbose,
671
863
  ccd=self.data,
672
- gain=self.lacosmic_params["run2_gain"]["value"],
673
- readnoise=self.lacosmic_params["run2_readnoise"]["value"],
864
+ gain_apply=True, # Always apply gain
674
865
  sigclip=self.lacosmic_params["run2_sigclip"]["value"],
675
866
  sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
676
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"],
677
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"],
678
880
  verbose=self.lacosmic_params["run2_verbose"]["value"],
881
+ inbkg=self.inbkg_data,
882
+ invar=self.invar_data,
679
883
  )
680
884
  # Apply usefulmask to consider only selected region
681
885
  cleandata_lacosmic2 *= usefulmask
@@ -743,7 +947,12 @@ class CosmicRayCleanerApp(ImageDisplay):
743
947
  last_dilation=self.lacosmic_params["dilation"]["value"],
744
948
  last_npoints=self.last_npoints,
745
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,
746
954
  auxdata=self.auxdata,
955
+ cleandata_lacosmic=self.cleandata_lacosmic,
747
956
  xmin=self.last_xmin,
748
957
  xmax=self.last_xmax,
749
958
  ymin=self.last_ymin,
@@ -760,8 +969,13 @@ class CosmicRayCleanerApp(ImageDisplay):
760
969
  if cleaning_method is None:
761
970
  print("Interpolation method selection cancelled. No cleaning applied!")
762
971
  return
972
+ # Update last employed parameters
763
973
  self.last_npoints = editor.npoints
764
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
765
979
  cleaning_region = SliceRegion2D(
766
980
  f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
767
981
  ).python
@@ -783,12 +997,19 @@ class CosmicRayCleanerApp(ImageDisplay):
783
997
  data_has_been_modified = True
784
998
  elif cleaning_method == "maskfill":
785
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
+ )
786
1006
  smoothed_output, _ = maskfill(
787
1007
  input_image=self.data,
788
1008
  mask=mask_crfound_region,
789
- size=3,
790
- operator="median",
791
- 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,
792
1013
  )
793
1014
  self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
794
1015
  # update mask_fixed to include the newly fixed pixels
@@ -891,11 +1112,11 @@ class CosmicRayCleanerApp(ImageDisplay):
891
1112
  if data_has_been_modified:
892
1113
  self.save_button.config(state=tk.NORMAL)
893
1114
  if self.num_features == 0:
894
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1115
+ self.review_detected_cr_button.config(state=tk.DISABLED)
895
1116
  self.replace_detected_cr_button.config(state=tk.DISABLED)
896
1117
  self.update_cr_overlay()
897
1118
 
898
- 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):
899
1120
  """Open a window to examine and possibly clean detected cosmic rays."""
900
1121
  self.working_in_review_window = True
901
1122
  review_window = tk.Toplevel(self.root)
@@ -906,6 +1127,8 @@ class CosmicRayCleanerApp(ImageDisplay):
906
1127
  tmp_cr_labels[iypix - 1, ixpix - 1] = 1
907
1128
  review = ReviewCosmicRay(
908
1129
  root=review_window,
1130
+ root_width=self.width,
1131
+ root_height=self.height,
909
1132
  data=self.data,
910
1133
  auxdata=self.auxdata,
911
1134
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -920,6 +1143,8 @@ class CosmicRayCleanerApp(ImageDisplay):
920
1143
  else:
921
1144
  review = ReviewCosmicRay(
922
1145
  root=review_window,
1146
+ root_width=self.width,
1147
+ root_height=self.height,
923
1148
  data=self.data,
924
1149
  auxdata=self.auxdata,
925
1150
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -961,7 +1186,7 @@ class CosmicRayCleanerApp(ImageDisplay):
961
1186
  if review.num_cr_cleaned > 0:
962
1187
  self.save_button.config(state=tk.NORMAL)
963
1188
  if self.num_features == 0:
964
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1189
+ self.review_detected_cr_button.config(state=tk.DISABLED)
965
1190
  self.replace_detected_cr_button.config(state=tk.DISABLED)
966
1191
  self.update_cr_overlay()
967
1192
 
@@ -1069,4 +1294,4 @@ class CosmicRayCleanerApp(ImageDisplay):
1069
1294
  else:
1070
1295
  ixpix = None
1071
1296
  iypix = None
1072
- 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)