teareduce 0.6.5__py3-none-any.whl → 0.6.6__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.
@@ -18,6 +18,34 @@ import sys
18
18
 
19
19
  from astropy.io import fits
20
20
 
21
+ try:
22
+ import PyCosmic
23
+ PYCOSMIC_AVAILABLE = True
24
+ except ModuleNotFoundError as e:
25
+ print("The 'teareduce.cleanest' module requires the 'PyCosmic' package.\n"
26
+ "Please install this module using:\n"
27
+ "`pip install git+https://github.com/nicocardiel/PyCosmic.git@test`"
28
+ )
29
+ PYCOSMIC_AVAILABLE = False
30
+
31
+ try:
32
+ import deepCR
33
+ except ModuleNotFoundError as e:
34
+ raise ModuleNotFoundError(
35
+ "The 'teareduce.cleanest' module requires the 'deepCR' package. "
36
+ "Please install teareduce with the 'cleanest' extra dependencies: "
37
+ "`pip install teareduce[cleanest]`."
38
+ ) from e
39
+
40
+ try:
41
+ import cosmic_conn
42
+ except ModuleNotFoundError as e:
43
+ raise ModuleNotFoundError(
44
+ "The 'teareduce.cleanest' module requires the 'cosmic-conn' package. "
45
+ "Please install teareduce with the 'cleanest' extra dependencies: "
46
+ "`pip install teareduce[cleanest]`."
47
+ ) from e
48
+
21
49
  try:
22
50
  from maskfill import maskfill
23
51
  except ModuleNotFoundError as e:
@@ -26,6 +54,8 @@ except ModuleNotFoundError as e:
26
54
  "Please install teareduce with the 'cleanest' extra dependencies: "
27
55
  "`pip install teareduce[cleanest]`."
28
56
  ) from e
57
+
58
+ from importlib.metadata import version, PackageNotFoundError
29
59
  import matplotlib.pyplot as plt
30
60
  from matplotlib.backend_bases import key_press_handler
31
61
  from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
@@ -37,7 +67,10 @@ from rich import print
37
67
 
38
68
  from .askextension import ask_extension_input_image
39
69
  from .centerchildparent import center_on_parent
70
+ from .definitions import cosmicconn_default_dict
71
+ from .definitions import deepcr_default_dict
40
72
  from .definitions import lacosmic_default_dict
73
+ from .definitions import pycosmic_default_dict
41
74
  from .definitions import DEFAULT_NPOINTS_INTERP
42
75
  from .definitions import DEFAULT_DEGREE_INTERP
43
76
  from .definitions import DEFAULT_MASKFILL_SIZE
@@ -59,7 +92,8 @@ from .imagedisplay import ImageDisplay
59
92
  from .lacosmicpad import lacosmicpad
60
93
  from .mergemasks import merge_peak_tail_masks
61
94
  from .modalprogressbar import ModalProgressBar
62
- from .parametereditor import ParameterEditor
95
+ from .parametereditor import ParameterEditorLACosmic
96
+ from .parametereditor import ParameterEditorPyCosmic
63
97
  from .reviewcosmicray import ReviewCosmicRay
64
98
  from .trackedbutton import TrackedTkButton
65
99
 
@@ -127,14 +161,26 @@ class CosmicRayCleanerApp(ImageDisplay):
127
161
  Save the cleaned data to a FITS file.
128
162
  create_widgets()
129
163
  Create the GUI widgets.
164
+ set_cursor_onoff()
165
+ Toggle the cursor mode on or off.
166
+ toggle_auxdata()
167
+ Toggle the use of auxiliary data.
168
+ toggle_aspect()
169
+ Toggle the aspect ratio of the displayed image.
130
170
  run_lacosmic()
131
171
  Run the L.A.Cosmic algorithm.
172
+ run_pycosmic()
173
+ Run the PyCosmic algorithm.
174
+ run_deepcr()
175
+ Run the DeepCR algorithm.
176
+ run_cosmicconn()
177
+ Run the Cosmic-CoNN algorithm.
132
178
  toggle_cr_overlay()
133
179
  Toggle the overlay of cosmic ray pixels on the image.
134
180
  update_cr_overlay()
135
181
  Update the overlay of cosmic ray pixels on the image.
136
- apply_lacosmic()
137
- Apply the L.A.Cosmic algorithm to the data.
182
+ apply_cleaning()
183
+ Apply selected cleaning algorithm to the data.
138
184
  review_detected_cr()
139
185
  Examine detected cosmic rays.
140
186
  stop_app()
@@ -160,8 +206,14 @@ class CosmicRayCleanerApp(ImageDisplay):
160
206
  Height of the GUI window in pixels.
161
207
  verbose : bool
162
208
  Enable verbose output.
209
+ cosmicconn_params : dict
210
+ Dictionary of Cosmic-CoNN parameters.
211
+ deepcr_params : dict
212
+ Dictionary of DeepCR parameters.
163
213
  lacosmic_params : dict
164
214
  Dictionary of L.A.Cosmic parameters.
215
+ pycosmic_params : dict
216
+ Dictionary of PyCosmic parameters.
165
217
  input_fits : str
166
218
  Path to the FITS file to be cleaned.
167
219
  extension : int
@@ -212,6 +264,10 @@ class CosmicRayCleanerApp(ImageDisplay):
212
264
  Last used verbose parameter for maskfill.
213
265
  cleandata_lacosmic : np.ndarray
214
266
  The cleaned data returned from L.A.Cosmic.
267
+ cleandata_pycosmic : np.ndarray
268
+ The cleaned data returned from PyCosmic.
269
+ cleandata_deepcr : np.ndarray
270
+ The cleaned data returned from DeepCR.
215
271
  cr_labels : np.ndarray
216
272
  Labeled cosmic ray features.
217
273
  num_features : int
@@ -233,9 +289,12 @@ class CosmicRayCleanerApp(ImageDisplay):
233
289
  self.default_font.configure(
234
290
  family=fontfamily, size=fontsize, weight="normal", slant="roman", underline=0, overstrike=0
235
291
  )
292
+ self.cosmicconn_params = cosmicconn_default_dict.copy()
293
+ self.deepcr_params = deepcr_default_dict.copy()
236
294
  self.lacosmic_params = lacosmic_default_dict.copy()
237
295
  self.lacosmic_params["run1_verbose"]["value"] = self.verbose
238
296
  self.lacosmic_params["run2_verbose"]["value"] = self.verbose
297
+ self.pycosmic_params = pycosmic_default_dict.copy()
239
298
  self.input_fits = input_fits
240
299
  self.extension = extension
241
300
  self.data = None
@@ -263,10 +322,276 @@ class CosmicRayCleanerApp(ImageDisplay):
263
322
  self.last_maskfill_verbose = DEFAULT_MASKFILL_VERBOSE
264
323
  self.create_widgets()
265
324
  self.cleandata_lacosmic = None
325
+ self.cleandata_pycosmic = None
326
+ self.cleandata_deepcr = None
266
327
  self.cr_labels = None
267
328
  self.num_features = 0
268
329
  self.working_in_review_window = False
269
330
 
331
+ def create_widgets(self):
332
+ """Create the GUI widgets.
333
+
334
+ Returns
335
+ -------
336
+ None
337
+
338
+ Notes
339
+ -----
340
+ This method sets up the GUI layout, including buttons for running
341
+ L.A.Cosmic, toggling cosmic ray overlay, applying cleaning methods,
342
+ examining detected cosmic rays, saving the cleaned FITS file, and
343
+ stopping the application. It also initializes the matplotlib figure
344
+ and canvas for image display, along with the toolbar for navigation.
345
+ The relevant attributes are stored in the instance for later use.
346
+ """
347
+ # Define instance of TrackedTkButton, that facilitates to show help information
348
+ # for each button displayed in the current application window.
349
+ tkbutton = TrackedTkButton(self.root)
350
+
351
+ # Row 1 of buttons
352
+ self.button_frame1 = tk.Frame(self.root)
353
+ self.button_frame1.pack(pady=5)
354
+ # --- L.A.Cosmic button
355
+ self.run_lacosmic_button = tkbutton.new(
356
+ self.button_frame1,
357
+ text="Run L.A.Cosmic",
358
+ command=self.run_lacosmic,
359
+ help_text="Run the L.A.Cosmic algorithm to detect cosmic rays in the image.",
360
+ )
361
+ self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
362
+ # --- PyCosmic button
363
+ self.run_pycosmic_button = tkbutton.new(
364
+ self.button_frame1,
365
+ text="Run PyCosmic",
366
+ command=self.run_pycosmic,
367
+ help_text="Run the PyCosmic algorithm to detect cosmic rays in the image.",
368
+ )
369
+ self.run_pycosmic_button.pack(side=tk.LEFT, padx=5)
370
+ if not PYCOSMIC_AVAILABLE:
371
+ self.run_pycosmic_button.config(state=tk.DISABLED)
372
+ # --- DeepCR button
373
+ self.run_deepcr_button = tkbutton.new(
374
+ self.button_frame1,
375
+ text="Run DeepCR",
376
+ command=self.run_deepcr,
377
+ help_text="Run the DeepCR algorithm to detect cosmic rays in the image.",
378
+ )
379
+ self.run_deepcr_button.pack(side=tk.LEFT, padx=5)
380
+ # --- Cosmic-CoNN button
381
+ self.run_cosmiccnn_button = tkbutton.new(
382
+ self.button_frame1,
383
+ text="Run Cosmic-CoNN",
384
+ command=self.run_cosmiccnn,
385
+ help_text="Run the Cosmic-CoNN algorithm to detect cosmic rays in the image.",
386
+ )
387
+ self.run_cosmiccnn_button.pack(side=tk.LEFT, padx=5)
388
+ # --- Stop program button
389
+ self.stop_button = tkbutton.new(
390
+ self.button_frame1, text="Stop program", command=self.stop_app, help_text="Stop the application."
391
+ )
392
+ self.stop_button.pack(side=tk.LEFT, padx=5)
393
+
394
+
395
+ # Row 2 of buttons
396
+ self.button_frame2 = tk.Frame(self.root)
397
+ self.button_frame2.pack(pady=5)
398
+ # --- Load auxdata button
399
+ self.load_auxdata_button = tkbutton.new(
400
+ self.button_frame2,
401
+ text="Load auxdata",
402
+ command=self.load_auxdata_from_file,
403
+ help_text="Load an auxiliary FITS file for display.",
404
+ )
405
+ self.load_auxdata_button.pack(side=tk.LEFT, padx=5)
406
+ # --- Load detected CR button
407
+ self.load_detected_cr_button = tkbutton.new(
408
+ self.button_frame2,
409
+ text="Load CR mask",
410
+ command=self.load_detected_cr_from_file,
411
+ help_text="Load a previously saved cosmic ray mask from a FITS file.",
412
+ )
413
+ self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
414
+ # --- Replace detected CR button
415
+ self.replace_detected_cr_button = tkbutton.new(
416
+ self.button_frame2,
417
+ text="Replace detected CRs",
418
+ command=self.apply_cleaning,
419
+ help_text="Apply the cleaning to the detected cosmic rays.",
420
+ )
421
+ self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
422
+ self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
423
+ # --- Review detected CR button
424
+ self.review_detected_cr_button = tkbutton.new(
425
+ self.button_frame2,
426
+ text="Review detected CRs",
427
+ command=lambda: self.review_detected_cr(1),
428
+ help_text="Review the detected cosmic rays.",
429
+ )
430
+ self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
431
+ self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
432
+
433
+ # Row 3 of buttons
434
+ self.button_frame3 = tk.Frame(self.root)
435
+ self.button_frame3.pack(pady=5)
436
+ # --- Cursor ON/OFF button
437
+ self.use_cursor = False
438
+ self.use_cursor_button = tkbutton.new(
439
+ self.button_frame3,
440
+ text="[c]ursor: OFF",
441
+ command=self.set_cursor_onoff,
442
+ help_text="Toggle the cursor ON or OFF (to select CR with mouse).",
443
+ alttext="[c]ursor: ??",
444
+ )
445
+ self.use_cursor_button.pack(side=tk.LEFT, padx=5)
446
+ # --- Toggle auxdata button
447
+ self.toggle_auxdata_button = tkbutton.new(
448
+ self.button_frame3,
449
+ text="[t]oggle data",
450
+ command=self.toggle_auxdata,
451
+ help_text="Toggle the display of auxiliary data.",
452
+ )
453
+ self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
454
+ if self.auxdata is None:
455
+ self.toggle_auxdata_button.config(state=tk.DISABLED)
456
+ else:
457
+ self.toggle_auxdata_button.config(state=tk.NORMAL)
458
+ # --- Toggle aspect button
459
+ self.image_aspect = "equal"
460
+ self.toggle_aspect_button = tkbutton.new(
461
+ self.button_frame3,
462
+ text=f"[a]spect: {self.image_aspect}",
463
+ command=self.toggle_aspect,
464
+ help_text="Toggle the image aspect ratio.",
465
+ )
466
+ self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
467
+ # --- Save cleaned FITS button
468
+ self.save_button = tkbutton.new(
469
+ self.button_frame3,
470
+ text="Save cleaned FITS",
471
+ command=self.save_fits_file,
472
+ help_text="Save the cleaned FITS file.",
473
+ )
474
+ self.save_button.pack(side=tk.LEFT, padx=5)
475
+ self.save_button.config(state=tk.DISABLED) # Initially disabled
476
+ # --- Save current mask button
477
+ self.save_crmask_button = tkbutton.new(
478
+ self.button_frame3,
479
+ text="Save CR mask",
480
+ command=self.save_crmask_to_file,
481
+ help_text="Save the current cosmic ray mask to a FITS file.",
482
+ )
483
+ self.save_crmask_button.pack(side=tk.LEFT, padx=5)
484
+ self.save_crmask_button.config(state=tk.DISABLED) # Initially disabled
485
+
486
+ # Row 4 of buttons
487
+ self.button_frame4 = tk.Frame(self.root)
488
+ self.button_frame4.pack(pady=5)
489
+ # --- vmin button
490
+ vmin, vmax = zscale(self.data)
491
+ self.vmin_button = tkbutton.new(
492
+ self.button_frame4,
493
+ text=f"vmin: {vmin:.2f}",
494
+ command=self.set_vmin,
495
+ help_text="Set the minimum value for the display scale.",
496
+ alttext="vmin: ??",
497
+ )
498
+ self.vmin_button.pack(side=tk.LEFT, padx=5)
499
+ # --- vmax button
500
+ self.vmax_button = tkbutton.new(
501
+ self.button_frame4,
502
+ text=f"vmax: {vmax:.2f}",
503
+ command=self.set_vmax,
504
+ help_text="Set the maximum value for the display scale.",
505
+ alttext="vmax: ??",
506
+ )
507
+ self.vmax_button.pack(side=tk.LEFT, padx=5)
508
+ # --- minmax button
509
+ self.set_minmax_button = tkbutton.new(
510
+ self.button_frame4,
511
+ text="minmax [,]",
512
+ command=self.set_minmax,
513
+ help_text="Set the minimum and maximum values for the display scale.",
514
+ )
515
+ self.set_minmax_button.pack(side=tk.LEFT, padx=5)
516
+ # --- zscale button
517
+ self.set_zscale_button = tkbutton.new(
518
+ self.button_frame4,
519
+ text="zscale [/]",
520
+ command=self.set_zscale,
521
+ help_text="Set the display scale using zscale.",
522
+ )
523
+ self.set_zscale_button.pack(side=tk.LEFT, padx=5)
524
+ # --- Overplot CR pixels button
525
+ if self.overplot_cr_pixels:
526
+ self.overplot_cr_button = tkbutton.new(
527
+ self.button_frame4,
528
+ text="CR overlay: ON ",
529
+ command=self.toggle_cr_overlay,
530
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
531
+ alttext="CR overlay: ??",
532
+ )
533
+ else:
534
+ self.overplot_cr_button = tkbutton.new(
535
+ self.button_frame4,
536
+ text="CR overlay: OFF",
537
+ command=self.toggle_cr_overlay,
538
+ help_text="Toggle the cosmic ray overlay ON or OFF.",
539
+ alttext="CR overlay: ??",
540
+ )
541
+ self.overplot_cr_button.pack(side=tk.LEFT, padx=5)
542
+ # --- Help button
543
+ self.help_button = tkbutton.new(
544
+ self.button_frame4,
545
+ text="Help",
546
+ command=tkbutton.show_help,
547
+ help_text="Show help information for all buttons.",
548
+ )
549
+ self.help_button.pack(side=tk.LEFT, padx=5)
550
+
551
+ # Figure
552
+ self.plot_frame = tk.Frame(self.root)
553
+ self.plot_frame.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
554
+ fig_dpi = 100
555
+ image_ratio = 480 / 640 # Default image ratio
556
+ fig_width_inches = self.width / fig_dpi
557
+ fig_height_inches = self.height * image_ratio / fig_dpi
558
+ self.fig, self.ax = plt.subplots(figsize=(fig_width_inches, fig_height_inches), dpi=fig_dpi)
559
+ self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
560
+ canvas_widget = self.canvas.get_tk_widget()
561
+ canvas_widget.config(width=self.width, height=self.height * image_ratio)
562
+ canvas_widget.pack(expand=True)
563
+ # The next two instructions prevent a segmentation fault when pressing "q"
564
+ self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
565
+ self.canvas.mpl_connect("key_press_event", self.on_key)
566
+ self.canvas.mpl_connect("button_press_event", self.on_click)
567
+ canvas_widget = self.canvas.get_tk_widget()
568
+
569
+ # Matplotlib toolbar
570
+ self.toolbar_frame = tk.Frame(self.root)
571
+ self.toolbar_frame.pack(fill=tk.X, expand=False, pady=5)
572
+ self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
573
+ self.toolbar.update()
574
+
575
+ # update the image display
576
+ xlabel = "X pixel (from 1 to NAXIS1)"
577
+ ylabel = "Y pixel (from 1 to NAXIS2)"
578
+ extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
579
+ self.image_aspect = "equal"
580
+ self.displaying_auxdata = False
581
+ self.image, _, _ = imshow(
582
+ fig=self.fig,
583
+ ax=self.ax,
584
+ data=self.data,
585
+ vmin=vmin,
586
+ vmax=vmax,
587
+ title=f"data: {os.path.basename(self.input_fits)}",
588
+ xlabel=xlabel,
589
+ ylabel=ylabel,
590
+ extent=extent,
591
+ aspect=self.image_aspect,
592
+ )
593
+ self.fig.tight_layout()
594
+
270
595
  def process_detected_cr(self, dilation):
271
596
  """Process the detected cosmic ray mask.
272
597
 
@@ -283,17 +608,12 @@ class CosmicRayCleanerApp(ImageDisplay):
283
608
  self.mask_crfound = dilatemask(mask=self.mask_crfound, iterations=dilation, connectivity=1)
284
609
  num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
285
610
  sdum = str(num_cr_pixels_after_dilation)
286
- else:
287
- sdum = str(num_cr_pixels_before_dilation)
288
- if dilation > 0:
289
- print(
290
- "Number of cosmic ray pixels before dilation.......: "
291
- f"{num_cr_pixels_before_dilation:>{len(sdum)}}"
292
- )
293
611
  print(
294
612
  f"Number of cosmic ray pixels after dilation........: "
295
613
  f"{num_cr_pixels_after_dilation:>{len(sdum)}}"
296
614
  )
615
+ else:
616
+ sdum = str(num_cr_pixels_before_dilation)
297
617
  # Label connected components in the mask; note that by default,
298
618
  # structure is a cross [0,1,0;1,1,1;0,1,0], but we want to consider
299
619
  # diagonal connections too, so we define a 3x3 square.
@@ -338,7 +658,14 @@ class CosmicRayCleanerApp(ImageDisplay):
338
658
  if extension not in hdul:
339
659
  raise KeyError(f"Extension name '{extension}' not found.")
340
660
  if hdul[extension].header["BITPIX"] not in [8, 16]:
341
- raise ValueError("Cosmic ray mask must be of integer type (BITPIX=8 or 16).")
661
+ answer = messagebox.askyesno(
662
+ f"Invalid Mask Data Type",
663
+ f"Invalid Mask Data Type: {hdul[extension].header['BITPIX']}\n"
664
+ "Cosmic ray mask is not of integer type (BITPIX=8 or 16).\n\n"
665
+ "Do you want to continue loading it anyway?",
666
+ )
667
+ if not answer:
668
+ return
342
669
  mask_crfound_loaded = hdul[extension].data.astype(bool)
343
670
  if mask_crfound_loaded.shape != self.data.shape:
344
671
  print(f"data shape...: {self.data.shape}")
@@ -350,8 +677,9 @@ class CosmicRayCleanerApp(ImageDisplay):
350
677
  "Dilation", "Enter Dilation (min=0):", initialvalue=0, minvalue=0
351
678
  )
352
679
  self.process_detected_cr(dilation=dilation)
353
- # self.lacosmic_params["dilation"]["value"] = dilation
680
+ self.cleandata_deepcr = None # Invalidate previous DeepCR cleaned data
354
681
  self.cleandata_lacosmic = None # Invalidate previous L.A.Cosmic cleaned data
682
+ self.cleandata_pycosmic = None # Invalidate previous PyCosmic cleaned data
355
683
  except Exception as e:
356
684
  messagebox.showerror("Error", f"Error loading cosmic ray mask: {e}")
357
685
 
@@ -508,222 +836,30 @@ class CosmicRayCleanerApp(ImageDisplay):
508
836
  except Exception as e:
509
837
  print(f"Error saving FITS file: {e}")
510
838
 
511
- def create_widgets(self):
512
- """Create the GUI widgets.
513
-
514
- Returns
515
- -------
516
- None
517
-
518
- Notes
519
- -----
520
- This method sets up the GUI layout, including buttons for running
521
- L.A.Cosmic, toggling cosmic ray overlay, applying cleaning methods,
522
- examining detected cosmic rays, saving the cleaned FITS file, and
523
- stopping the application. It also initializes the matplotlib figure
524
- and canvas for image display, along with the toolbar for navigation.
525
- The relevant attributes are stored in the instance for later use.
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
-
531
- # Row 1 of buttons
532
- self.button_frame1 = tk.Frame(self.root)
533
- self.button_frame1.pack(pady=5)
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
- )
540
- self.run_lacosmic_button.pack(side=tk.LEFT, padx=5)
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.",
553
- )
554
- self.load_detected_cr_button.pack(side=tk.LEFT, padx=5)
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.",
560
- )
561
- self.replace_detected_cr_button.pack(side=tk.LEFT, padx=5)
562
- self.replace_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
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.",
568
- )
569
- self.review_detected_cr_button.pack(side=tk.LEFT, padx=5)
570
- self.review_detected_cr_button.config(state=tk.DISABLED) # Initially disabled
571
-
572
- # Row 2 of buttons
573
- self.button_frame2 = tk.Frame(self.root)
574
- self.button_frame2.pack(pady=5)
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
- )
590
- self.toggle_auxdata_button.pack(side=tk.LEFT, padx=5)
591
- if self.auxdata is None:
592
- self.toggle_auxdata_button.config(state=tk.DISABLED)
593
- else:
594
- self.toggle_auxdata_button.config(state=tk.NORMAL)
595
- self.image_aspect = "equal"
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.",
601
- )
602
- self.toggle_aspect_button.pack(side=tk.LEFT, padx=5)
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
- )
609
- self.save_button.pack(side=tk.LEFT, padx=5)
610
- self.save_button.config(state=tk.DISABLED) # Initially disabled
611
- self.stop_button = tkbutton.new(
612
- self.button_frame2, text="Stop program", command=self.stop_app, help_text="Stop the application."
613
- )
614
- self.stop_button.pack(side=tk.LEFT, padx=5)
615
-
616
- # Row 3 of buttons
617
- self.button_frame3 = tk.Frame(self.root)
618
- self.button_frame3.pack(pady=5)
619
- vmin, vmax = zscale(self.data)
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
- )
627
- self.vmin_button.pack(side=tk.LEFT, padx=5)
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
- )
635
- self.vmax_button.pack(side=tk.LEFT, padx=5)
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
- )
642
- self.set_minmax_button.pack(side=tk.LEFT, padx=5)
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
- )
649
- self.set_zscale_button.pack(side=tk.LEFT, padx=5)
650
- if self.overplot_cr_pixels:
651
- self.overplot_cr_button = tkbutton.new(
652
- self.button_frame3,
653
- text="CR overlay: ON ",
654
- command=self.toggle_cr_overlay,
655
- help_text="Toggle the cosmic ray overlay ON or OFF.",
656
- alttext="CR overlay: ??",
657
- )
658
- else:
659
- self.overplot_cr_button = tkbutton.new(
660
- self.button_frame3,
661
- text="CR overlay: OFF",
662
- command=self.toggle_cr_overlay,
663
- help_text="Toggle the cosmic ray overlay ON or OFF.",
664
- alttext="CR overlay: ??",
665
- )
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)
674
-
675
- # Figure
676
- self.plot_frame = tk.Frame(self.root)
677
- self.plot_frame.pack(padx=5, pady=5, fill=tk.BOTH, expand=True)
678
- fig_dpi = 100
679
- image_ratio = 480 / 640 # Default image ratio
680
- fig_width_inches = self.width / fig_dpi
681
- fig_height_inches = self.height * image_ratio / fig_dpi
682
- self.fig, self.ax = plt.subplots(figsize=(fig_width_inches, fig_height_inches), dpi=fig_dpi)
683
- self.canvas = FigureCanvasTkAgg(self.fig, master=self.plot_frame)
684
- canvas_widget = self.canvas.get_tk_widget()
685
- canvas_widget.config(width=self.width, height=self.height * image_ratio)
686
- canvas_widget.pack(expand=True)
687
- # The next two instructions prevent a segmentation fault when pressing "q"
688
- self.canvas.mpl_disconnect(self.canvas.mpl_connect("key_press_event", key_press_handler))
689
- self.canvas.mpl_connect("key_press_event", self.on_key)
690
- self.canvas.mpl_connect("button_press_event", self.on_click)
691
- canvas_widget = self.canvas.get_tk_widget()
692
-
693
- # Matplotlib toolbar
694
- self.toolbar_frame = tk.Frame(self.root)
695
- self.toolbar_frame.pack(fill=tk.X, expand=False, pady=5)
696
- self.toolbar = NavigationToolbar2Tk(self.canvas, self.toolbar_frame)
697
- self.toolbar.update()
698
-
699
- # update the image display
700
- xlabel = "X pixel (from 1 to NAXIS1)"
701
- ylabel = "Y pixel (from 1 to NAXIS2)"
702
- extent = [0.5, self.data.shape[1] + 0.5, 0.5, self.data.shape[0] + 0.5]
703
- self.image_aspect = "equal"
704
- self.displaying_auxdata = False
705
- self.image, _, _ = imshow(
706
- fig=self.fig,
707
- ax=self.ax,
708
- data=self.data,
709
- vmin=vmin,
710
- vmax=vmax,
711
- title=f"data: {os.path.basename(self.input_fits)}",
712
- xlabel=xlabel,
713
- ylabel=ylabel,
714
- extent=extent,
715
- aspect=self.image_aspect,
716
- )
717
- self.fig.tight_layout()
718
-
719
- def set_cursor_onoff(self):
720
- """Toggle cursor selection mode on or off."""
721
- if not self.use_cursor:
722
- self.use_cursor = True
723
- self.use_cursor_button.config(text="[c]ursor: ON ")
724
- else:
725
- self.use_cursor = False
726
- self.use_cursor_button.config(text="[c]ursor: OFF")
839
+ def save_crmask_to_file(self):
840
+ """Save the current cosmic ray mask to a FITS file."""
841
+ output_fits = filedialog.asksaveasfilename(
842
+ initialdir=os.getcwd(),
843
+ title="Save last CR mask into a FITS file",
844
+ defaultextension=".fits",
845
+ filetypes=[("FITS files", "*.fits"), ("All files", "*.*")],
846
+ initialfile=None,
847
+ )
848
+ try:
849
+ hdu = fits.PrimaryHDU(self.mask_crfound.astype(np.uint8))
850
+ hdu.writeto(output_fits, overwrite=True)
851
+ print(f"Last mask saved to {output_fits}")
852
+ except Exception as e:
853
+ print(f"Error saving mask into a FITS file: {e}")
854
+
855
+ def set_cursor_onoff(self):
856
+ """Toggle cursor selection mode on or off."""
857
+ if not self.use_cursor:
858
+ self.use_cursor = True
859
+ self.use_cursor_button.config(text="[c]ursor: ON ")
860
+ else:
861
+ self.use_cursor = False
862
+ self.use_cursor_button.config(text="[c]ursor: OFF")
727
863
 
728
864
  def toggle_auxdata(self):
729
865
  """Toggle between main data and auxiliary data for display."""
@@ -767,13 +903,14 @@ class CosmicRayCleanerApp(ImageDisplay):
767
903
  if not overwrite:
768
904
  return
769
905
  self.run_lacosmic_button.config(state=tk.DISABLED)
906
+ print("[bold green]Define L.A.Cosmic parameters...[/bold green]")
770
907
  # Define parameters for L.A.Cosmic from default dictionary
771
908
  editor_window = tk.Toplevel(self.root)
772
909
  center_on_parent(child=editor_window, parent=self.root)
773
- editor = ParameterEditor(
910
+ editor = ParameterEditorLACosmic(
774
911
  root=editor_window,
775
912
  param_dict=self.lacosmic_params,
776
- window_title="Cosmic Ray Mask Generation Parameters",
913
+ window_title="Mask Generation Parameters (L.A.Cosmic)",
777
914
  xmin=self.last_xmin,
778
915
  xmax=self.last_xmax,
779
916
  ymin=self.last_ymin,
@@ -834,6 +971,7 @@ class CosmicRayCleanerApp(ImageDisplay):
834
971
  cleandata_lacosmic, mask_crfound = lacosmicpad(
835
972
  pad_width=borderpadd,
836
973
  show_arguments=self.verbose,
974
+ display_ccdproc_version=True,
837
975
  ccd=self.data,
838
976
  gain_apply=True, # Always apply gain
839
977
  sigclip=self.lacosmic_params["run1_sigclip"]["value"],
@@ -864,6 +1002,7 @@ class CosmicRayCleanerApp(ImageDisplay):
864
1002
  cleandata_lacosmic2, mask_crfound2 = lacosmicpad(
865
1003
  pad_width=borderpadd,
866
1004
  show_arguments=self.verbose,
1005
+ display_ccdproc_version=False,
867
1006
  ccd=self.data,
868
1007
  gain_apply=True, # Always apply gain
869
1008
  sigclip=self.lacosmic_params["run2_sigclip"]["value"],
@@ -900,9 +1039,257 @@ class CosmicRayCleanerApp(ImageDisplay):
900
1039
  self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
901
1040
  # Process the mask: dilation and labeling
902
1041
  self.process_detected_cr(dilation=self.lacosmic_params["dilation"]["value"])
1042
+ # Invalidate previous cleaned data from other methods
1043
+ self.cleandata_deepcr = None
1044
+ self.cleandata_pycosmic = None
903
1045
  else:
904
1046
  print("Parameter editing cancelled. L.A.Cosmic detection skipped!")
905
1047
  self.run_lacosmic_button.config(state=tk.NORMAL)
1048
+ self.save_crmask_button.config(state=tk.NORMAL)
1049
+
1050
+ def run_pycosmic(self):
1051
+ """Run PyCosmic to detect cosmic rays."""
1052
+ if np.any(self.mask_crfound):
1053
+ overwrite = messagebox.askyesno(
1054
+ "Overwrite Cosmic Ray Mask",
1055
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
1056
+ )
1057
+ if not overwrite:
1058
+ return
1059
+ self.run_pycosmic_button.config(state=tk.DISABLED)
1060
+ print("[bold green]Define PyCosmic parameters...[/bold green]")
1061
+ # Define parameters for PyCosmic from default dictionary
1062
+ editor_window = tk.Toplevel(self.root)
1063
+ center_on_parent(child=editor_window, parent=self.root)
1064
+ editor = ParameterEditorPyCosmic(
1065
+ root=editor_window,
1066
+ param_dict=self.pycosmic_params,
1067
+ window_title="Mask Generation Parameters (PyCosmic)",
1068
+ xmin=self.last_xmin,
1069
+ xmax=self.last_xmax,
1070
+ ymin=self.last_ymin,
1071
+ ymax=self.last_ymax,
1072
+ imgshape=self.data.shape,
1073
+ )
1074
+ # Make it modal (blocks interaction with main window)
1075
+ editor_window.transient(self.root)
1076
+ editor_window.grab_set()
1077
+ # Wait for the editor window to close
1078
+ self.root.wait_window(editor_window)
1079
+ # Get the result after window closes
1080
+ updated_params = editor.get_result()
1081
+ if updated_params is not None:
1082
+ # Update last used region values
1083
+ self.last_xmin = updated_params["xmin"]["value"]
1084
+ self.last_xmax = updated_params["xmax"]["value"]
1085
+ self.last_ymin = updated_params["ymin"]["value"]
1086
+ self.last_ymax = updated_params["ymax"]["value"]
1087
+ usefulregion = SliceRegion2D(
1088
+ f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]", mode="fits"
1089
+ ).python
1090
+ usefulmask = np.zeros_like(self.data)
1091
+ usefulmask[usefulregion] = 1.0
1092
+ # Update parameter dictionary with new values
1093
+ self.pycosmic_params = updated_params
1094
+ if self.verbose:
1095
+ print("Parameters updated:")
1096
+ for key, info in self.pycosmic_params.items():
1097
+ print(f" {key}: {info['value']}")
1098
+ if self.pycosmic_params["nruns"]["value"] not in [1, 2]:
1099
+ raise ValueError("nruns must be 1 or 2")
1100
+ # Execute PyCosmic with updated parameters
1101
+ try:
1102
+ pycosmic_version = version("PyCosmic")
1103
+ except Exception:
1104
+ pycosmic_version = "unknown"
1105
+ print(f"Using PyCosmic version: {pycosmic_version}")
1106
+ if self.pycosmic_params["run1_verbose"]["value"]:
1107
+ end = "\n"
1108
+ for key, info in self.pycosmic_params.items():
1109
+ print(f"PyCosmic parameter: {key} = {info['value']}")
1110
+ else:
1111
+ end = ""
1112
+ print("[bold green]Executing PyCosmic (run 1)...[/bold green]")
1113
+ print(f"(please wait...) ", end=end)
1114
+ out = PyCosmic.det_cosmics(
1115
+ data=self.data,
1116
+ sigma_det=self.pycosmic_params["run1_sigma_det"]["value"],
1117
+ rlim=self.pycosmic_params["run1_rlim"]["value"],
1118
+ iterations=self.pycosmic_params["run1_iterations"]["value"],
1119
+ fwhm_gauss=[
1120
+ self.pycosmic_params["run1_fwhm_gauss_x"]["value"],
1121
+ self.pycosmic_params["run1_fwhm_gauss_y"]["value"],
1122
+ ],
1123
+ replace_box=[
1124
+ self.pycosmic_params["run1_replace_box_x"]["value"],
1125
+ self.pycosmic_params["run1_replace_box_y"]["value"],
1126
+ ],
1127
+ replace_error=self.pycosmic_params["run1_replace_error"]["value"],
1128
+ increase_radius=self.pycosmic_params["run1_increase_radius"]["value"],
1129
+ gain=self.pycosmic_params["run1_gain"]["value"],
1130
+ rdnoise=self.pycosmic_params["run1_rdnoise"]["value"],
1131
+ bias=self.pycosmic_params["run1_bias"]["value"],
1132
+ verbose=self.pycosmic_params["run1_verbose"]["value"],
1133
+ )
1134
+ print(f"Done!")
1135
+ cleandata_pycosmic = out.data
1136
+ mask_crfound = out.mask.astype(bool)
1137
+ # Apply usefulmask to consider only selected region
1138
+ cleandata_pycosmic *= usefulmask
1139
+ mask_crfound = mask_crfound & (usefulmask.astype(bool))
1140
+ # Second execution if nruns == 2
1141
+ if self.pycosmic_params["nruns"]["value"] == 2:
1142
+ print("[bold green]Executing PyCosmic (run 2)...[/bold green]")
1143
+ print(f"(please wait...) ", end=end)
1144
+ out2 = PyCosmic.det_cosmics(
1145
+ data=self.data,
1146
+ sigma_det=self.pycosmic_params["run2_sigma_det"]["value"],
1147
+ rlim=self.pycosmic_params["run2_rlim"]["value"],
1148
+ iterations=self.pycosmic_params["run2_iterations"]["value"],
1149
+ fwhm_gauss=[
1150
+ self.pycosmic_params["run2_fwhm_gauss_x"]["value"],
1151
+ self.pycosmic_params["run2_fwhm_gauss_y"]["value"],
1152
+ ],
1153
+ replace_box=[
1154
+ self.pycosmic_params["run2_replace_box_x"]["value"],
1155
+ self.pycosmic_params["run2_replace_box_y"]["value"],
1156
+ ],
1157
+ replace_error=self.pycosmic_params["run2_replace_error"]["value"],
1158
+ increase_radius=self.pycosmic_params["run2_increase_radius"]["value"],
1159
+ gain=self.pycosmic_params["run2_gain"]["value"],
1160
+ rdnoise=self.pycosmic_params["run2_rdnoise"]["value"],
1161
+ bias=self.pycosmic_params["run2_bias"]["value"],
1162
+ verbose=self.pycosmic_params["run2_verbose"]["value"],
1163
+ )
1164
+ print(f"Done!")
1165
+ cleandata_pycosmic2 = out2.data
1166
+ mask_crfound2 = out2.mask.astype(bool)
1167
+ # Apply usefulmask to consider only selected region
1168
+ cleandata_pycosmic2 *= usefulmask
1169
+ mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
1170
+ # Combine results from both runs
1171
+ if np.any(mask_crfound):
1172
+ mask_crfound = merge_peak_tail_masks(mask_crfound, mask_crfound2, verbose=True)
1173
+ # Use the cleandata from the second run
1174
+ cleandata_pycosmic = cleandata_pycosmic2
1175
+ # Select the image region to process
1176
+ self.cleandata_pycosmic = self.data.copy()
1177
+ self.cleandata_pycosmic[usefulregion] = cleandata_pycosmic[usefulregion]
1178
+ self.mask_crfound = np.zeros_like(self.data, dtype=bool)
1179
+ self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
1180
+ # Process the mask: labeling (dilation is not necessary for PyCosmic; this
1181
+ # algorithm already includes a parameter 'increase_radius' to grow the detected CRs)
1182
+ self.process_detected_cr(dilation=0)
1183
+ # Invalidate previous cleaned data from other methods
1184
+ self.cleandata_lacosmic = None
1185
+ self.cleandata_deepcr = None
1186
+ else:
1187
+ print("Parameter editing cancelled. PyCosmic detection skipped!")
1188
+ self.run_pycosmic_button.config(state=tk.NORMAL)
1189
+ self.save_crmask_button.config(state=tk.NORMAL)
1190
+
1191
+ def run_deepcr(self):
1192
+ """Run DeepCR to detect cosmic rays."""
1193
+ if np.any(self.mask_crfound):
1194
+ overwrite = messagebox.askyesno(
1195
+ "Overwrite Cosmic Ray Mask",
1196
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
1197
+ )
1198
+ if not overwrite:
1199
+ return
1200
+ self.run_deepcr_button.config(state=tk.DISABLED)
1201
+ print("[bold green]Executing DeepCR...[/bold green]")
1202
+ # Initialize the DeepCR model
1203
+ mdl = deepCR.deepCR(mask=self.deepcr_params["mask"]["value"])
1204
+ # Ask for threshold value and update parameter
1205
+ threshold = simpledialog.askfloat(
1206
+ "Threshold",
1207
+ "Enter DeepCR probability threshold (0.0 - 1.0):",
1208
+ initialvalue=self.deepcr_params["threshold"]["value"],
1209
+ minvalue=0.0,
1210
+ maxvalue=1.0,
1211
+ )
1212
+ if threshold is None:
1213
+ print("Threshold input cancelled. DeepCR detection skipped!")
1214
+ self.run_deepcr_button.config(state=tk.NORMAL)
1215
+ return
1216
+ self.deepcr_params["threshold"]["value"] = threshold
1217
+ print(f"Running DeepCR version: {deepCR.__version__} (please wait...)", end="")
1218
+ self.mask_crfound, self.cleandata_deepcr = mdl.clean(
1219
+ self.data,
1220
+ threshold=self.deepcr_params["threshold"]["value"],
1221
+ inpaint=True,
1222
+ )
1223
+ print(" Done!")
1224
+ # Process the mask: dilation and labeling
1225
+ dilation = simpledialog.askinteger(
1226
+ "Dilation",
1227
+ "Note: Applying dilation will prevent the use of the DeepCR cleaned data.\n\n" "Enter Dilation (min=0):",
1228
+ initialvalue=self.deepcr_params["dilation"]["value"],
1229
+ minvalue=0,
1230
+ )
1231
+ if dilation is None:
1232
+ print("Dilation input cancelled. DeepCR detection skipped!")
1233
+ self.run_deepcr_button.config(state=tk.NORMAL)
1234
+ return
1235
+ if dilation > 0:
1236
+ self.cleandata_deepcr = None # Invalidate DeepCR cleaned data if dilation applied
1237
+ self.deepcr_params["dilation"]["value"] = dilation
1238
+ self.process_detected_cr(dilation=self.deepcr_params["dilation"]["value"])
1239
+ # Invalidate previous cleaned data from other methods
1240
+ self.cleandata_lacosmic = None
1241
+ self.cleandata_pycosmic = None
1242
+ self.run_deepcr_button.config(state=tk.NORMAL)
1243
+ self.save_crmask_button.config(state=tk.NORMAL)
1244
+
1245
+ def run_cosmiccnn(self):
1246
+ """Run Cosmic-CoNN to detect cosmic rays."""
1247
+ if np.any(self.mask_crfound):
1248
+ overwrite = messagebox.askyesno(
1249
+ "Overwrite Cosmic Ray Mask",
1250
+ "A cosmic ray mask is already defined.\n\nDo you want to overwrite it?",
1251
+ )
1252
+ if not overwrite:
1253
+ return
1254
+ self.run_cosmiccnn_button.config(state=tk.DISABLED)
1255
+ print("[bold green]Executing Cosmic-CoNN...[/bold green]")
1256
+ # Initialize the generic ground-imaging model
1257
+ cr_model = cosmic_conn.init_model("ground_imaging")
1258
+ # The model outputs a CR probability map
1259
+ print(f"Running Cosmic-CoNN version: {cosmic_conn.__version__} (please wait...)", end="")
1260
+ cr_prob = cr_model.detect_cr(self.data.astype(np.float32))
1261
+ print(" Done!")
1262
+ # Ask for threshold value and update parameter
1263
+ threshold = simpledialog.askfloat(
1264
+ "Threshold",
1265
+ "Enter Cosmic-CoNN probability threshold (0.0 - 1.0):",
1266
+ initialvalue=self.cosmicconn_params["threshold"]["value"],
1267
+ minvalue=0.0,
1268
+ maxvalue=1.0,
1269
+ )
1270
+ if threshold is None:
1271
+ print("Threshold input cancelled. Cosmic-CoNN detection skipped!")
1272
+ self.run_cosmiccnn_button.config(state=tk.NORMAL)
1273
+ return
1274
+ self.cosmicconn_params["threshold"]["value"] = threshold
1275
+ # Threshold the probability map to create a binary mask
1276
+ self.mask_crfound = cr_prob > self.cosmicconn_params["threshold"]["value"]
1277
+ # Process the mask: dilation and labeling
1278
+ dilation = simpledialog.askinteger(
1279
+ "Dilation", "Enter Dilation (min=0):", initialvalue=self.cosmicconn_params["dilation"]["value"], minvalue=0
1280
+ )
1281
+ if dilation is None:
1282
+ print("Dilation input cancelled. Cosmic-CoNN detection skipped!")
1283
+ self.run_cosmiccnn_button.config(state=tk.NORMAL)
1284
+ return
1285
+ self.cosmicconn_params["dilation"]["value"] = dilation
1286
+ self.process_detected_cr(dilation=self.cosmicconn_params["dilation"]["value"])
1287
+ # Invalidate previous cleaned data from other methods
1288
+ self.cleandata_lacosmic = None
1289
+ self.cleandata_pycosmic = None
1290
+ self.cleandata_deepcr = None
1291
+ self.run_cosmiccnn_button.config(state=tk.NORMAL)
1292
+ self.save_crmask_button.config(state=tk.NORMAL)
906
1293
 
907
1294
  def toggle_cr_overlay(self):
908
1295
  """Toggle the overlay of cosmic ray pixels on the image."""
@@ -931,7 +1318,7 @@ class CosmicRayCleanerApp(ImageDisplay):
931
1318
  del self.scatter_cr
932
1319
  self.canvas.draw_idle()
933
1320
 
934
- def apply_lacosmic(self):
1321
+ def apply_cleaning(self):
935
1322
  """Apply the selected cleaning method to the detected cosmic rays."""
936
1323
  if np.any(self.mask_crfound):
937
1324
  # recalculate labels and number of features
@@ -951,6 +1338,8 @@ class CosmicRayCleanerApp(ImageDisplay):
951
1338
  last_maskfill_verbose=self.last_maskfill_verbose,
952
1339
  auxdata=self.auxdata,
953
1340
  cleandata_lacosmic=self.cleandata_lacosmic,
1341
+ cleandata_pycosmic=self.cleandata_pycosmic,
1342
+ cleandata_deepcr=self.cleandata_deepcr,
954
1343
  xmin=self.last_xmin,
955
1344
  xmax=self.last_xmax,
956
1345
  ymin=self.last_ymin,
@@ -993,6 +1382,22 @@ class CosmicRayCleanerApp(ImageDisplay):
993
1382
  # upate mask_crfound by eliminating the cleaned pixels
994
1383
  self.mask_crfound[mask_crfound_region] = False
995
1384
  data_has_been_modified = True
1385
+ elif cleaning_method == "pycosmic":
1386
+ # Replace detected CR pixels with PyCosmic values
1387
+ self.data[mask_crfound_region] = self.cleandata_pycosmic[mask_crfound_region]
1388
+ # update mask_fixed to include the newly fixed pixels
1389
+ self.mask_fixed[mask_crfound_region] = True
1390
+ # upate mask_crfound by eliminating the cleaned pixels
1391
+ self.mask_crfound[mask_crfound_region] = False
1392
+ data_has_been_modified = True
1393
+ elif cleaning_method == "deepcr":
1394
+ # Replace detected CR pixels with DeepCR values
1395
+ self.data[mask_crfound_region] = self.cleandata_deepcr[mask_crfound_region]
1396
+ # update mask_fixed to include the newly fixed pixels
1397
+ self.mask_fixed[mask_crfound_region] = True
1398
+ # upate mask_crfound by eliminating the cleaned pixels
1399
+ self.mask_crfound[mask_crfound_region] = False
1400
+ data_has_been_modified = True
996
1401
  elif cleaning_method == "maskfill":
997
1402
  # Replace detected CR pixels with local median values
998
1403
  print(
@@ -1098,7 +1503,7 @@ class CosmicRayCleanerApp(ImageDisplay):
1098
1503
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
1099
1504
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
1100
1505
  num_cr_remaining = np.sum(self.mask_crfound)
1101
- sdum = str(num_cr_remaining)
1506
+ sdum = str(int(num_cr_remaining + 0.5))
1102
1507
  print(f"Remaining number of cosmic ray pixels...................: {sdum}")
1103
1508
  print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
1104
1509
  if num_cr_remaining == 0:
@@ -1130,6 +1535,8 @@ class CosmicRayCleanerApp(ImageDisplay):
1130
1535
  data=self.data,
1131
1536
  auxdata=self.auxdata,
1132
1537
  cleandata_lacosmic=self.cleandata_lacosmic,
1538
+ cleandata_pycosmic=self.cleandata_pycosmic,
1539
+ cleandata_deepcr=self.cleandata_deepcr,
1133
1540
  cr_labels=tmp_cr_labels,
1134
1541
  num_features=1,
1135
1542
  first_cr_index=1,
@@ -1146,6 +1553,8 @@ class CosmicRayCleanerApp(ImageDisplay):
1146
1553
  data=self.data,
1147
1554
  auxdata=self.auxdata,
1148
1555
  cleandata_lacosmic=self.cleandata_lacosmic,
1556
+ cleandata_pycosmic=self.cleandata_pycosmic,
1557
+ cleandata_deepcr=self.cleandata_deepcr,
1149
1558
  cr_labels=self.cr_labels,
1150
1559
  num_features=self.num_features,
1151
1560
  first_cr_index=first_cr_index,
@@ -1172,7 +1581,7 @@ class CosmicRayCleanerApp(ImageDisplay):
1172
1581
  structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
1173
1582
  self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
1174
1583
  num_remaining = np.sum(self.mask_crfound)
1175
- sdum = str(num_remaining)
1584
+ sdum = str(int(num_remaining + 0.5))
1176
1585
  print(f"Remaining number of cosmic ray pixels...................: {sdum}")
1177
1586
  print(f"Remaining number of cosmic ray features (grouped pixels): {self.num_features:>{len(sdum)}}")
1178
1587
  if num_remaining == 0: