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.
- teareduce/cleanest/__main__.py +4 -10
- teareduce/cleanest/cosmicraycleanerapp.py +642 -233
- teareduce/cleanest/definitions.py +63 -5
- teareduce/cleanest/interpolate.py +1 -1
- teareduce/cleanest/interpolationeditor.py +23 -9
- teareduce/cleanest/lacosmicpad.py +23 -4
- teareduce/cleanest/parametereditor.py +386 -5
- teareduce/cleanest/reviewcosmicray.py +109 -18
- teareduce/sdistortion.py +1 -1
- teareduce/statsummary.py +1 -1
- teareduce/version.py +1 -1
- teareduce/wavecal.py +1 -1
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/METADATA +5 -1
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/RECORD +18 -18
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/WHEEL +0 -0
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/entry_points.txt +0 -0
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.6.5.dist-info → teareduce-0.6.6.dist-info}/top_level.txt +0 -0
|
@@ -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
|
|
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
|
-
|
|
137
|
-
Apply
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
512
|
-
"""
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
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 =
|
|
910
|
+
editor = ParameterEditorLACosmic(
|
|
774
911
|
root=editor_window,
|
|
775
912
|
param_dict=self.lacosmic_params,
|
|
776
|
-
window_title="
|
|
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
|
|
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:
|