teareduce 0.5.9__py3-none-any.whl → 0.6.1__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,20 +280,18 @@ 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
- )
260
288
  if dilation > 0:
261
289
  print(
262
- f"Number of cosmic ray pixels after dilation....: "
290
+ "Number of cosmic ray pixels before dilation.......: "
291
+ f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
292
+ )
293
+ print(
294
+ f"Number of cosmic ray pixels after dilation........: "
263
295
  f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
264
296
  )
265
297
  # Label connected components in the mask; note that by default,
@@ -267,9 +299,9 @@ class CosmicRayCleanerApp(ImageDisplay):
267
299
  # diagonal connections too, so we define a 3x3 square.
268
300
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
269
301
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
270
- print(f"Number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
302
+ print(f"Number of cosmic ray features (grouped pixels)....: {self.num_features:>{len(sdum)}}")
271
303
  self.replace_detected_cr_button.config(state=tk.NORMAL)
272
- self.examine_detected_cr_button.config(state=tk.NORMAL)
304
+ self.review_detected_cr_button.config(state=tk.NORMAL)
273
305
  self.update_cr_overlay()
274
306
  self.use_cursor = True
275
307
  self.use_cursor_button.config(text="[c]ursor: ON ")
@@ -278,10 +310,17 @@ class CosmicRayCleanerApp(ImageDisplay):
278
310
  self.cr_labels = None
279
311
  self.num_features = 0
280
312
  self.replace_detected_cr_button.config(state=tk.DISABLED)
281
- self.examine_detected_cr_button.config(state=tk.DISABLED)
313
+ self.review_detected_cr_button.config(state=tk.DISABLED)
282
314
 
283
315
  def load_detected_cr_from_file(self):
284
316
  """Load detected cosmic ray mask from a FITS file."""
317
+ if np.any(self.mask_crfound):
318
+ overwrite = messagebox.askyesno(
319
+ "Overwrite Cosmic Ray Mask",
320
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
321
+ )
322
+ if not overwrite:
323
+ return
285
324
  crmask_file = filedialog.askopenfilename(
286
325
  initialdir=os.getcwd(),
287
326
  title="Select FITS file with cosmic ray mask",
@@ -289,18 +328,7 @@ class CosmicRayCleanerApp(ImageDisplay):
289
328
  )
290
329
  if crmask_file:
291
330
  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
- )
331
+ extension = ask_extension_input_image(crmask_file, self.data.shape)
304
332
  try:
305
333
  with fits.open(crmask_file, mode="readonly") as hdul:
306
334
  if isinstance(extension, int):
@@ -309,6 +337,8 @@ class CosmicRayCleanerApp(ImageDisplay):
309
337
  else:
310
338
  if extension not in hdul:
311
339
  raise KeyError(f"Extension name '{extension}' not found.")
340
+ if hdul[extension].header["BITPIX"] not in [8, 16]:
341
+ raise ValueError("Cosmic ray mask must be of integer type (BITPIX=8 or 16).")
312
342
  mask_crfound_loaded = hdul[extension].data.astype(bool)
313
343
  if mask_crfound_loaded.shape != self.data.shape:
314
344
  print(f"data shape...: {self.data.shape}")
@@ -316,9 +346,14 @@ class CosmicRayCleanerApp(ImageDisplay):
316
346
  raise ValueError("Cosmic ray mask has different shape.")
317
347
  self.mask_crfound = mask_crfound_loaded
318
348
  print(f"Loaded cosmic ray mask from {crmask_file}")
349
+ dilation = simpledialog.askinteger(
350
+ "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
351
+ )
319
352
  self.process_detected_cr(dilation=dilation)
353
+ # self.lacosmic_params["dilation"]["value"] = dilation
354
+ self.cleandata_lacosmic = None # Invalidate previous L.A.Cosmic cleaned data
320
355
  except Exception as e:
321
- print(f"Error loading cosmic ray mask: {e}")
356
+ messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
322
357
 
323
358
  def load_fits_file(self):
324
359
  """Load the FITS file and auxiliary file (if provided).
@@ -378,7 +413,9 @@ class CosmicRayCleanerApp(ImageDisplay):
378
413
  else:
379
414
  if self.extension_auxfile not in hdul_aux:
380
415
  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}")
416
+ print(
417
+ f"Reading auxiliary file [bold green]{self.auxfile}[/bold green], extension {self.extension_auxfile}"
418
+ )
382
419
  self.auxdata = hdul_aux[self.extension_auxfile].data
383
420
  if self.auxdata.shape != self.data.shape:
384
421
  print(f"data shape...: {self.data.shape}")
@@ -387,6 +424,43 @@ class CosmicRayCleanerApp(ImageDisplay):
387
424
  except Exception as e:
388
425
  sys.exit(f"Error loading auxiliary FITS file: {e}")
389
426
 
427
+ def load_auxdata_from_file(self):
428
+ """Load auxiliary data from a FITS file."""
429
+ if self.auxfile is not None:
430
+ overwrite = messagebox.askyesno(
431
+ "Overwrite Auxiliary Data",
432
+ f"An auxiliary file is already loaded:\n\n{self.auxfile}\n\n" "Do you want to overwrite it?",
433
+ )
434
+ if not overwrite:
435
+ return
436
+ auxfile = filedialog.askopenfilename(
437
+ initialdir=os.getcwd(),
438
+ title="Select auxiliary FITS file",
439
+ filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
440
+ )
441
+ if auxfile:
442
+ extension = ask_extension_input_image(auxfile, self.data.shape)
443
+ try:
444
+ with fits.open(auxfile, mode="readonly") as hdul:
445
+ if isinstance(extension, int):
446
+ if extension < 0 or extension >= len(hdul):
447
+ raise IndexError(f"Extension index {extension} out of range.")
448
+ else:
449
+ if extension not in hdul:
450
+ raise KeyError(f"Extension name '{extension}' not found.")
451
+ auxdata_loaded = hdul[extension].data
452
+ if auxdata_loaded.shape != self.data.shape:
453
+ print(f"data shape...: {self.data.shape}")
454
+ print(f"auxdata shape: {auxdata_loaded.shape}")
455
+ raise ValueError("Auxiliary file has different shape.")
456
+ self.auxfile = auxfile
457
+ self.auxdata = auxdata_loaded
458
+ self.extension_auxfile = extension
459
+ print(f"Loaded auxiliary data from {auxfile}")
460
+ self.toggle_auxdata_button.config(state=tk.NORMAL)
461
+ except Exception as e:
462
+ print(f"Error loading auxiliary FITS file: {e}")
463
+
390
464
  def save_fits_file(self):
391
465
  """Save the cleaned FITS file.
392
466
 
@@ -450,74 +524,153 @@ class CosmicRayCleanerApp(ImageDisplay):
450
524
  and canvas for image display, along with the toolbar for navigation.
451
525
  The relevant attributes are stored in the instance for later use.
452
526
  """
527
+ # Define instance of TrackedTkButton, that facilitates to show help information
528
+ # for each button displayed in the current application window.
529
+ tkbutton = TrackedTkButton(self.root)
530
+
453
531
  # Row 1 of buttons
454
532
  self.button_frame1 = tk.Frame(self.root)
455
533
  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)
534
+ self.run_lacosmic_button = tkbutton.new(
535
+ self.button_frame1,
536
+ text="Run L.A.Cosmic",
537
+ command=self.run_lacosmic,
538
+ help_text="Run the L.A.Cosmic algorithm to detect cosmic rays in the image.",
539
+ )
457
540
  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
541
+ self.load_auxdata_button = tkbutton.new(
542
+ self.button_frame1,
543
+ text="Load auxdata",
544
+ command=self.load_auxdata_from_file,
545
+ help_text="Load an auxiliary FITS file for display.",
546
+ )
547
+ self.load_auxdata_button.pack(side=tk.LEFT, padx=5)
548
+ self.load_detected_cr_button = tkbutton.new(
549
+ self.button_frame1,
550
+ text="Load CR mask",
551
+ command=self.load_detected_cr_from_file,
552
+ help_text="Load a previously saved cosmic ray mask from a FITS file.",
460
553
  )
461
554
  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
555
+ self.replace_detected_cr_button = tkbutton.new(
556
+ self.button_frame1,
557
+ text="Replace detected CRs",
558
+ command=self.apply_lacosmic,
559
+ help_text="Apply the cleaning to the detected cosmic rays.",
464
560
  )
465
561
  self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
466
562
  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)
563
+ self.review_detected_cr_button = tkbutton.new(
564
+ self.button_frame1,
565
+ text="Review detected CRs",
566
+ command=lambda: self.review_detected_cr(1),
567
+ help_text="Review the detected cosmic rays.",
469
568
  )
470
- self.examine_detected_cr_button.pack(side=tk.LEFT, padx=5)
471
- self.examine_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
569
+ self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
570
+ self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
472
571
 
473
572
  # Row 2 of buttons
474
573
  self.button_frame2 = tk.Frame(self.root)
475
574
  self.button_frame2.pack(pady=5)
476
- self.toggle_auxdata_button = tk.Button(self.button_frame2, text="[t]oggle data", command=self.toggle_auxdata)
575
+ self.use_cursor = False
576
+ self.use_cursor_button = tkbutton.new(
577
+ self.button_frame2,
578
+ text="[c]ursor: OFF",
579
+ command=self.set_cursor_onoff,
580
+ help_text="Toggle the cursor ON or OFF (to select CR with mouse).",
581
+ alttext="[c]ursor: ??",
582
+ )
583
+ self.use_cursor_button.pack(side=tk.LEFT, padx=5)
584
+ self.toggle_auxdata_button = tkbutton.new(
585
+ self.button_frame2,
586
+ text="[t]oggle data",
587
+ command=self.toggle_auxdata,
588
+ help_text="Toggle the display of auxiliary data.",
589
+ )
477
590
  self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
478
591
  if self.auxdata is None:
479
592
  self.toggle_auxdata_button.config(state=tk.DISABLED)
480
593
  else:
481
594
  self.toggle_auxdata_button.config(state=tk.NORMAL)
482
595
  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
596
+ self.toggle_aspect_button = tkbutton.new(
597
+ self.button_frame2,
598
+ text=f"[a]spect: {self.image_aspect}",
599
+ command=self.toggle_aspect,
600
+ help_text="Toggle the image aspect ratio.",
485
601
  )
486
602
  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)
603
+ self.save_button = tkbutton.new(
604
+ self.button_frame2,
605
+ text="Save cleaned FITS",
606
+ command=self.save_fits_file,
607
+ help_text="Save the cleaned FITS file.",
608
+ )
488
609
  self.save_button.pack(side=tk.LEFT, padx=5)
489
610
  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)
611
+ self.stop_button = tkbutton.new(
612
+ self.button_frame2, text="Stop program", command=self.stop_app, help_text="Stop the application."
613
+ )
491
614
  self.stop_button.pack(side=tk.LEFT, padx=5)
492
615
 
493
616
  # Row 3 of buttons
494
617
  self.button_frame3 = tk.Frame(self.root)
495
618
  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
619
  vmin, vmax = zscale(self.data)
500
- self.vmin_button = tk.Button(self.button_frame3, text=f"vmin: {vmin:.2f}", command=self.set_vmin)
620
+ self.vmin_button = tkbutton.new(
621
+ self.button_frame3,
622
+ text=f"vmin: {vmin:.2f}",
623
+ command=self.set_vmin,
624
+ help_text="Set the minimum value for the display scale.",
625
+ alttext="vmin: ??",
626
+ )
501
627
  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)
628
+ self.vmax_button = tkbutton.new(
629
+ self.button_frame3,
630
+ text=f"vmax: {vmax:.2f}",
631
+ command=self.set_vmax,
632
+ help_text="Set the maximum value for the display scale.",
633
+ alttext="vmax: ??",
634
+ )
503
635
  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)
636
+ self.set_minmax_button = tkbutton.new(
637
+ self.button_frame3,
638
+ text="minmax [,]",
639
+ command=self.set_minmax,
640
+ help_text="Set the minimum and maximum values for the display scale.",
641
+ )
505
642
  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)
643
+ self.set_zscale_button = tkbutton.new(
644
+ self.button_frame3,
645
+ text="zscale [/]",
646
+ command=self.set_zscale,
647
+ help_text="Set the display scale using zscale.",
648
+ )
507
649
  self.set_zscale_button.pack(side=tk.LEFT, padx=5)
508
650
  if self.overplot_cr_pixels:
509
- self.overplot_cr_button = tk.Button(
651
+ self.overplot_cr_button = tkbutton.new(
510
652
  self.button_frame3,
511
653
  text="CR overlay: ON ",
512
654
  command=self.toggle_cr_overlay,
655
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
656
+ alttext="CR overlay: ??",
513
657
  )
514
658
  else:
515
- self.overplot_cr_button = tk.Button(
659
+ self.overplot_cr_button = tkbutton.new(
516
660
  self.button_frame3,
517
661
  text="CR overlay: OFF",
518
662
  command=self.toggle_cr_overlay,
663
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
664
+ alttext="CR overlay: ??",
519
665
  )
520
666
  self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
667
+ self.help_button = tkbutton.new(
668
+ self.button_frame3,
669
+ text="Help",
670
+ command=tkbutton.show_help,
671
+ help_text="Show help information for all buttons.",
672
+ )
673
+ self.help_button.pack(side=tk.LEFT, padx=5)
521
674
 
522
675
  # Figure
523
676
  self.plot_frame = tk.Frame(self.root)
@@ -606,6 +759,13 @@ class CosmicRayCleanerApp(ImageDisplay):
606
759
 
607
760
  def run_lacosmic(self):
608
761
  """Run L.A.Cosmic to detect cosmic rays."""
762
+ if np.any(self.mask_crfound):
763
+ overwrite = messagebox.askyesno(
764
+ "Overwrite Cosmic Ray Mask",
765
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
766
+ )
767
+ if not overwrite:
768
+ return
609
769
  self.run_lacosmic_button.config(state=tk.DISABLED)
610
770
  # Define parameters for L.A.Cosmic from default dictionary
611
771
  editor_window = tk.Toplevel(self.root)
@@ -619,6 +779,10 @@ class CosmicRayCleanerApp(ImageDisplay):
619
779
  ymin=self.last_ymin,
620
780
  ymax=self.last_ymax,
621
781
  imgshape=self.data.shape,
782
+ inbkg=self.last_inbkg,
783
+ extnum_inbkg=self.last_extnum_inbkg,
784
+ invar=self.last_invar,
785
+ extnum_invar=self.last_extnum_invar,
622
786
  )
623
787
  # Make it modal (blocks interaction with main window)
624
788
  editor_window.transient(self.root)
@@ -633,6 +797,24 @@ class CosmicRayCleanerApp(ImageDisplay):
633
797
  self.last_xmax = updated_params["xmax"]["value"]
634
798
  self.last_ymin = updated_params["ymin"]["value"]
635
799
  self.last_ymax = updated_params["ymax"]["value"]
800
+ self.last_inbkg = updated_params["inbkg"]["value"]
801
+ self.last_extnum_inbkg = updated_params["extnum_inbkg"]["value"]
802
+ self.last_invar = updated_params["invar"]["value"]
803
+ self.last_extnum_invar = updated_params["extnum_invar"]["value"]
804
+ if self.last_inbkg is not None:
805
+ with fits.open(self.last_inbkg, mode="readonly") as hdul_inbkg:
806
+ if self.last_extnum_inbkg < 0 or self.last_extnum_inbkg >= len(hdul_inbkg):
807
+ raise IndexError(f"Extension index {self.last_extnum_inbkg} out of range.")
808
+ self.inbkg_data = hdul_inbkg[self.last_extnum_inbkg].data.astype(np.float32)
809
+ else:
810
+ self.inbkg_data = None
811
+ if self.last_invar is not None:
812
+ with fits.open(self.last_invar, mode="readonly") as hdul_invar:
813
+ if self.last_extnum_invar < 0 or self.last_extnum_invar >= len(hdul_invar):
814
+ raise IndexError(f"Extension index {self.last_extnum_invar} out of range.")
815
+ self.invar_data = hdul_invar[self.last_extnum_invar].data.astype(np.float32)
816
+ else:
817
+ self.invar_data = None
636
818
  usefulregion = SliceRegion2D(
637
819
  f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
638
820
  ).python
@@ -651,14 +833,27 @@ class CosmicRayCleanerApp(ImageDisplay):
651
833
  borderpadd = updated_params["borderpadd"]["value"]
652
834
  cleandata_lacosmic, mask_crfound = lacosmicpad(
653
835
  pad_width=borderpadd,
836
+ show_arguments=self.verbose,
654
837
  ccd=self.data,
655
- gain=self.lacosmic_params["run1_gain"]["value"],
656
- readnoise=self.lacosmic_params["run1_readnoise"]["value"],
838
+ gain_apply=True, # Always apply gain
657
839
  sigclip=self.lacosmic_params["run1_sigclip"]["value"],
658
840
  sigfrac=self.lacosmic_params["run1_sigfrac"]["value"],
659
841
  objlim=self.lacosmic_params["run1_objlim"]["value"],
842
+ gain=self.lacosmic_params["run1_gain"]["value"],
843
+ readnoise=self.lacosmic_params["run1_readnoise"]["value"],
844
+ satlevel=self.lacosmic_params["run1_satlevel"]["value"],
660
845
  niter=self.lacosmic_params["run1_niter"]["value"],
846
+ sepmed=self.lacosmic_params["run1_sepmed"]["value"],
847
+ cleantype=self.lacosmic_params["run1_cleantype"]["value"],
848
+ fsmode=self.lacosmic_params["run1_fsmode"]["value"],
849
+ psfmodel=self.lacosmic_params["run1_psfmodel"]["value"],
850
+ psffwhm_x=self.lacosmic_params["run1_psffwhm_x"]["value"],
851
+ psffwhm_y=self.lacosmic_params["run1_psffwhm_y"]["value"],
852
+ psfsize=self.lacosmic_params["run1_psfsize"]["value"],
853
+ psfbeta=self.lacosmic_params["run1_psfbeta"]["value"],
661
854
  verbose=self.lacosmic_params["run1_verbose"]["value"],
855
+ inbkg=self.inbkg_data,
856
+ invar=self.invar_data,
662
857
  )
663
858
  # Apply usefulmask to consider only selected region
664
859
  cleandata_lacosmic *= usefulmask
@@ -668,24 +863,34 @@ class CosmicRayCleanerApp(ImageDisplay):
668
863
  print("[bold green]Executing L.A.Cosmic (run 2)...[/bold green]")
669
864
  cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
670
865
  pad_width=borderpadd,
866
+ show_arguments=self.verbose,
671
867
  ccd=self.data,
672
- gain=self.lacosmic_params["run2_gain"]["value"],
673
- readnoise=self.lacosmic_params["run2_readnoise"]["value"],
868
+ gain_apply=True, # Always apply gain
674
869
  sigclip=self.lacosmic_params["run2_sigclip"]["value"],
675
870
  sigfrac=self.lacosmic_params["run2_sigfrac"]["value"],
676
871
  objlim=self.lacosmic_params["run2_objlim"]["value"],
872
+ gain=self.lacosmic_params["run2_gain"]["value"],
873
+ readnoise=self.lacosmic_params["run2_readnoise"]["value"],
874
+ satlevel=self.lacosmic_params["run2_satlevel"]["value"],
677
875
  niter=self.lacosmic_params["run2_niter"]["value"],
876
+ sepmed=self.lacosmic_params["run2_sepmed"]["value"],
877
+ cleantype=self.lacosmic_params["run2_cleantype"]["value"],
878
+ fsmode=self.lacosmic_params["run2_fsmode"]["value"],
879
+ psfmodel=self.lacosmic_params["run2_psfmodel"]["value"],
880
+ psffwhm_x=self.lacosmic_params["run2_psffwhm_x"]["value"],
881
+ psffwhm_y=self.lacosmic_params["run2_psffwhm_y"]["value"],
882
+ psfsize=self.lacosmic_params["run2_psfsize"]["value"],
883
+ psfbeta=self.lacosmic_params["run2_psfbeta"]["value"],
678
884
  verbose=self.lacosmic_params["run2_verbose"]["value"],
885
+ inbkg=self.inbkg_data,
886
+ invar=self.invar_data,
679
887
  )
680
888
  # Apply usefulmask to consider only selected region
681
889
  cleandata_lacosmic2 *= usefulmask
682
890
  mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
683
891
  # Combine results from both runs
684
892
  if np.any(mask_crfound):
685
- print(f"Number of cosmic ray pixels (run1).......: {np.sum(mask_crfound)}")
686
- print(f"Number of cosmic ray pixels (run2).......: {np.sum(mask_crfound2)}")
687
- mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2)
688
- print(f"Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}")
893
+ mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2, verbose=True)
689
894
  # Use the cleandata from the second run
690
895
  cleandata_lacosmic = cleandata_lacosmic2
691
896
  # Select the image region to process
@@ -732,9 +937,6 @@ class CosmicRayCleanerApp(ImageDisplay):
732
937
  # recalculate labels and number of features
733
938
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
734
939
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
735
- sdum = str(np.sum(self.mask_crfound))
736
- print(f"Number of cosmic ray pixels detected by L.A.Cosmic...........: {sdum}")
737
- print(f"Number of cosmic rays (grouped pixels) detected by L.A.Cosmic: {self.num_features:>{len(sdum)}}")
738
940
  # Define parameters for L.A.Cosmic from default dictionary
739
941
  editor_window = tk.Toplevel(self.root)
740
942
  center_on_parent(child=editor_window, parent=self.root)
@@ -743,7 +945,12 @@ class CosmicRayCleanerApp(ImageDisplay):
743
945
  last_dilation=self.lacosmic_params["dilation"]["value"],
744
946
  last_npoints=self.last_npoints,
745
947
  last_degree=self.last_degree,
948
+ last_maskfill_size=self.last_maskfill_size,
949
+ last_maskfill_operator=self.last_maskfill_operator,
950
+ last_maskfill_smooth=self.last_maskfill_smooth,
951
+ last_maskfill_verbose=self.last_maskfill_verbose,
746
952
  auxdata=self.auxdata,
953
+ cleandata_lacosmic=self.cleandata_lacosmic,
747
954
  xmin=self.last_xmin,
748
955
  xmax=self.last_xmax,
749
956
  ymin=self.last_ymin,
@@ -760,8 +967,13 @@ class CosmicRayCleanerApp(ImageDisplay):
760
967
  if cleaning_method is None:
761
968
  print("Interpolation method selection cancelled. No cleaning applied!")
762
969
  return
970
+ # Update last employed parameters
763
971
  self.last_npoints = editor.npoints
764
972
  self.last_degree = editor.degree
973
+ self.last_maskfill_size = editor.maskfill_size
974
+ self.last_maskfill_operator = editor.maskfill_operator
975
+ self.last_maskfill_smooth = editor.maskfill_smooth
976
+ self.last_maskfill_verbose = editor.maskfill_verbose
765
977
  cleaning_region = SliceRegion2D(
766
978
  f"[{editor.xmin}:{editor.xmax},{editor.ymin}:{editor.ymax}]", mode="fits"
767
979
  ).python
@@ -783,12 +995,19 @@ class CosmicRayCleanerApp(ImageDisplay):
783
995
  data_has_been_modified = True
784
996
  elif cleaning_method == "maskfill":
785
997
  # Replace detected CR pixels with local median values
998
+ print(
999
+ f"Maskfill parameters: size={self.last_maskfill_size}, "
1000
+ f"operator={self.last_maskfill_operator}, "
1001
+ f"smooth={self.last_maskfill_smooth}, "
1002
+ f"verbose={self.last_maskfill_verbose}"
1003
+ )
786
1004
  smoothed_output, _ = maskfill(
787
1005
  input_image=self.data,
788
1006
  mask=mask_crfound_region,
789
- size=3,
790
- operator="median",
791
- smooth=True,
1007
+ size=self.last_maskfill_size,
1008
+ operator=self.last_maskfill_operator,
1009
+ smooth=self.last_maskfill_smooth,
1010
+ verbose=self.last_maskfill_verbose,
792
1011
  )
793
1012
  self.data[mask_crfound_region] = smoothed_output[mask_crfound_region]
794
1013
  # update mask_fixed to include the newly fixed pixels
@@ -891,11 +1110,11 @@ class CosmicRayCleanerApp(ImageDisplay):
891
1110
  if data_has_been_modified:
892
1111
  self.save_button.config(state=tk.NORMAL)
893
1112
  if self.num_features == 0:
894
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1113
+ self.review_detected_cr_button.config(state=tk.DISABLED)
895
1114
  self.replace_detected_cr_button.config(state=tk.DISABLED)
896
1115
  self.update_cr_overlay()
897
1116
 
898
- def examine_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
1117
+ def review_detected_cr(self, first_cr_index=1, single_cr=False, ixpix=None, iypix=None):
899
1118
  """Open a window to examine and possibly clean detected cosmic rays."""
900
1119
  self.working_in_review_window = True
901
1120
  review_window = tk.Toplevel(self.root)
@@ -906,6 +1125,8 @@ class CosmicRayCleanerApp(ImageDisplay):
906
1125
  tmp_cr_labels[iypix - 1, ixpix - 1] = 1
907
1126
  review = ReviewCosmicRay(
908
1127
  root=review_window,
1128
+ root_width=self.width,
1129
+ root_height=self.height,
909
1130
  data=self.data,
910
1131
  auxdata=self.auxdata,
911
1132
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -920,6 +1141,8 @@ class CosmicRayCleanerApp(ImageDisplay):
920
1141
  else:
921
1142
  review = ReviewCosmicRay(
922
1143
  root=review_window,
1144
+ root_width=self.width,
1145
+ root_height=self.height,
923
1146
  data=self.data,
924
1147
  auxdata=self.auxdata,
925
1148
  cleandata_lacosmic=self.cleandata_lacosmic,
@@ -961,7 +1184,7 @@ class CosmicRayCleanerApp(ImageDisplay):
961
1184
  if review.num_cr_cleaned > 0:
962
1185
  self.save_button.config(state=tk.NORMAL)
963
1186
  if self.num_features == 0:
964
- self.examine_detected_cr_button.config(state=tk.DISABLED)
1187
+ self.review_detected_cr_button.config(state=tk.DISABLED)
965
1188
  self.replace_detected_cr_button.config(state=tk.DISABLED)
966
1189
  self.update_cr_overlay()
967
1190
 
@@ -1069,4 +1292,4 @@ class CosmicRayCleanerApp(ImageDisplay):
1069
1292
  else:
1070
1293
  ixpix = None
1071
1294
  iypix = None
1072
- self.examine_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)
1295
+ self.review_detected_cr(label_at_click, single_cr=True, ixpix=ixpix, iypix=iypix)