teareduce 0.5.2__tar.gz → 0.5.4__tar.gz
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-0.5.2/src/teareduce.egg-info → teareduce-0.5.4}/PKG-INFO +1 -1
- teareduce-0.5.4/src/teareduce/cleanest/__init__.py +10 -0
- teareduce-0.5.4/src/teareduce/cleanest/cleanest.py +139 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/cosmicraycleanerapp.py +90 -30
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/definitions.py +20 -9
- teareduce-0.5.4/src/teareduce/cleanest/dilatemask.py +41 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/interpolation_a.py +29 -7
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/interpolation_x.py +18 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/interpolation_y.py +18 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/interpolationeditor.py +4 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/parametereditor.py +67 -22
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/reviewcosmicray.py +34 -13
- teareduce-0.5.4/src/teareduce/tests/test_cleanest.py +131 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/version.py +1 -1
- {teareduce-0.5.2 → teareduce-0.5.4/src/teareduce.egg-info}/PKG-INFO +1 -1
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce.egg-info/SOURCES.txt +3 -0
- teareduce-0.5.2/src/teareduce/tests/__init__.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/LICENSE.txt +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/README.md +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/pyproject.toml +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/setup.cfg +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/__init__.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/avoid_astropy_warnings.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/__main__.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/find_closest_true.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cleanest/imagedisplay.py +0 -0
- {teareduce-0.5.2/src/teareduce/cleanest → teareduce-0.5.4/src/teareduce/cookbook}/__init__.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cookbook/get_cookbook_file.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/correct_pincushion_distortion.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/cosmicrays.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/ctext.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/draw_rectangle.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/elapsed_time.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/histogram1d.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/imshow.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/numsplines.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/peaks_spectrum.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/polfit.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/robust_std.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/sdistortion.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/simulateccdexposure.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/sliceregion.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/statsummary.py +0 -0
- {teareduce-0.5.2/src/teareduce/cookbook → teareduce-0.5.4/src/teareduce/tests}/__init__.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/tests/test_sliceregion.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/tests/test_version.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/wavecal.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/write_array_to_fits.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce/zscale.py +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce.egg-info/dependency_links.txt +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce.egg-info/entry_points.txt +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce.egg-info/requires.txt +0 -0
- {teareduce-0.5.2 → teareduce-0.5.4}/src/teareduce.egg-info/top_level.txt +0 -0
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0+
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Interpolate pixels identified in a mask."""
|
|
11
|
+
|
|
12
|
+
from scipy import ndimage
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from .dilatemask import dilatemask
|
|
16
|
+
from .interpolation_x import interpolation_x
|
|
17
|
+
from .interpolation_y import interpolation_y
|
|
18
|
+
from .interpolation_a import interpolation_a
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def cleanest(data, mask_crfound, dilation=0,
|
|
22
|
+
interp_method=None, npoints=None, degree=None,
|
|
23
|
+
debug=False):
|
|
24
|
+
"""Interpolate pixels identified in a mask.
|
|
25
|
+
|
|
26
|
+
The original data and mask are not modified. A copy of both
|
|
27
|
+
arrays are created and returned with the interpolated pixels.
|
|
28
|
+
|
|
29
|
+
Parameters
|
|
30
|
+
----------
|
|
31
|
+
data : 2D numpy.ndarray
|
|
32
|
+
The image data array to be processed.
|
|
33
|
+
mask_crfound : 2D numpy.ndarray of bool
|
|
34
|
+
A boolean mask array indicating which pixels are affected by
|
|
35
|
+
cosmic rays.
|
|
36
|
+
dilation : int, optional
|
|
37
|
+
The number of pixels to dilate the masked pixels before
|
|
38
|
+
interpolation.
|
|
39
|
+
interp_method : str, optional
|
|
40
|
+
The interpolation method to use. Options are:
|
|
41
|
+
'x' : Polynomial interpolation in the X direction.
|
|
42
|
+
'y' : Polynomial interpolation in the Y direction.
|
|
43
|
+
's' : Surface fit (degree 1) interpolation.
|
|
44
|
+
'd' : Median of border pixels interpolation.
|
|
45
|
+
'm' : Mean of border pixels interpolation.
|
|
46
|
+
npoints : int, optional
|
|
47
|
+
The number of points to use for interpolation. This
|
|
48
|
+
parameter is relevant for 'x', 'y', 's', 'd', and 'm' methods.
|
|
49
|
+
degree : int, optional
|
|
50
|
+
The degree of the polynomial to fit. This parameter is
|
|
51
|
+
relevant for 'x' and 'y' methods.
|
|
52
|
+
debug : bool, optional
|
|
53
|
+
If True, print debug information.
|
|
54
|
+
|
|
55
|
+
Returns
|
|
56
|
+
-------
|
|
57
|
+
cleaned_data : 2D numpy.ndarray
|
|
58
|
+
The image data array with cosmic rays cleaned.
|
|
59
|
+
mask_fixed : 2D numpy.ndarray of bool
|
|
60
|
+
The updated boolean mask array indicating which pixels
|
|
61
|
+
have been fixed.
|
|
62
|
+
|
|
63
|
+
Notes
|
|
64
|
+
-----
|
|
65
|
+
This function has been created to clean cosmic rays without
|
|
66
|
+
the need of a GUI interaction. It can be used in scripts
|
|
67
|
+
or batch processing of images.
|
|
68
|
+
"""
|
|
69
|
+
if interp_method is None:
|
|
70
|
+
raise ValueError("interp_method must be specified.")
|
|
71
|
+
if interp_method not in ['x', 'y', 's', 'd', 'm']:
|
|
72
|
+
raise ValueError(f"Unknown interp_method: {interp_method}")
|
|
73
|
+
if npoints is None:
|
|
74
|
+
raise ValueError("npoints must be specified.")
|
|
75
|
+
if degree is None and interp_method in ['x', 'y']:
|
|
76
|
+
raise ValueError("degree must be specified for the chosen interp_method.")
|
|
77
|
+
|
|
78
|
+
# Apply dilation to the cosmic ray mask if needed
|
|
79
|
+
if dilation > 0:
|
|
80
|
+
updated_mask_crfound = dilatemask(mask_crfound, dilation)
|
|
81
|
+
else:
|
|
82
|
+
updated_mask_crfound = mask_crfound.copy()
|
|
83
|
+
|
|
84
|
+
# Create a mask to keep track of cleaned pixels
|
|
85
|
+
mask_fixed = np.zeros_like(mask_crfound, dtype=bool)
|
|
86
|
+
|
|
87
|
+
# Determine number of CR features
|
|
88
|
+
structure = [[1, 1, 1],
|
|
89
|
+
[1, 1, 1],
|
|
90
|
+
[1, 1, 1]]
|
|
91
|
+
cr_labels, num_features = ndimage.label(updated_mask_crfound, structure=structure)
|
|
92
|
+
if debug:
|
|
93
|
+
print(f"Number of cosmic ray pixels to be cleaned: {np.sum(updated_mask_crfound)}")
|
|
94
|
+
print(f"Number of cosmic rays (grouped pixels)...: {num_features}")
|
|
95
|
+
|
|
96
|
+
# Fix cosmic rays using the specified interpolation method
|
|
97
|
+
cleaned_data = data.copy()
|
|
98
|
+
num_cr_cleaned = 0
|
|
99
|
+
for cr_index in range(1, num_features + 1):
|
|
100
|
+
if interp_method in ['x', 'y']:
|
|
101
|
+
if 2 * npoints <= degree:
|
|
102
|
+
raise ValueError("2*npoints must be greater than degree for polynomial interpolation.")
|
|
103
|
+
if interp_method == 'x':
|
|
104
|
+
interp_func = interpolation_x
|
|
105
|
+
else:
|
|
106
|
+
interp_func = interpolation_y
|
|
107
|
+
interpolation_performed, _, _ = interp_func(
|
|
108
|
+
data=cleaned_data,
|
|
109
|
+
mask_fixed=mask_fixed,
|
|
110
|
+
cr_labels=cr_labels,
|
|
111
|
+
cr_index=cr_index,
|
|
112
|
+
npoints=npoints,
|
|
113
|
+
degree=degree)
|
|
114
|
+
if interpolation_performed:
|
|
115
|
+
num_cr_cleaned += 1
|
|
116
|
+
elif interp_method in ['s', 'd', 'm']:
|
|
117
|
+
if interp_method == 's':
|
|
118
|
+
method = 'surface'
|
|
119
|
+
elif interp_method == 'd':
|
|
120
|
+
method = 'median'
|
|
121
|
+
elif interp_method == 'm':
|
|
122
|
+
method = 'mean'
|
|
123
|
+
interpolation_performed, _, _ = interpolation_a(
|
|
124
|
+
data=cleaned_data,
|
|
125
|
+
mask_fixed=mask_fixed,
|
|
126
|
+
cr_labels=cr_labels,
|
|
127
|
+
cr_index=cr_index,
|
|
128
|
+
npoints=npoints,
|
|
129
|
+
method=method
|
|
130
|
+
)
|
|
131
|
+
if interpolation_performed:
|
|
132
|
+
num_cr_cleaned += 1
|
|
133
|
+
else:
|
|
134
|
+
raise ValueError(f"Unknown interpolation method: {interp_method}")
|
|
135
|
+
|
|
136
|
+
if debug:
|
|
137
|
+
print(f"Number of cosmic rays cleaned............: {num_cr_cleaned}")
|
|
138
|
+
|
|
139
|
+
return cleaned_data, mask_fixed
|
|
@@ -19,15 +19,17 @@ from ccdproc import cosmicray_lacosmic
|
|
|
19
19
|
import matplotlib.pyplot as plt
|
|
20
20
|
from matplotlib.backend_bases import key_press_handler
|
|
21
21
|
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg, NavigationToolbar2Tk
|
|
22
|
+
from scipy import ndimage
|
|
22
23
|
import numpy as np
|
|
23
24
|
import os
|
|
24
25
|
from rich import print
|
|
25
|
-
from
|
|
26
|
+
from tqdm import tqdm
|
|
26
27
|
|
|
27
28
|
from .definitions import lacosmic_default_dict
|
|
28
29
|
from .definitions import DEFAULT_NPOINTS_INTERP
|
|
29
30
|
from .definitions import DEFAULT_DEGREE_INTERP
|
|
30
31
|
from .definitions import MAX_PIXEL_DISTANCE_TO_CR
|
|
32
|
+
from .dilatemask import dilatemask
|
|
31
33
|
from .find_closest_true import find_closest_true
|
|
32
34
|
from .interpolation_a import interpolation_a
|
|
33
35
|
from .interpolation_x import interpolation_x
|
|
@@ -134,8 +136,11 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
134
136
|
Flag to indicate if the review window is active.
|
|
135
137
|
"""
|
|
136
138
|
self.root = root
|
|
139
|
+
# self.root.geometry("800x800+50+0") # This does not work in Fedora
|
|
140
|
+
self.root.minsize(800, 800)
|
|
141
|
+
self.root.update_idletasks()
|
|
142
|
+
self.root.geometry("+50+0")
|
|
137
143
|
self.root.title("Cosmic Ray Cleaner")
|
|
138
|
-
self.root.geometry("800x700+50+0")
|
|
139
144
|
self.lacosmic_params = lacosmic_default_dict.copy()
|
|
140
145
|
self.input_fits = input_fits
|
|
141
146
|
self.extension = extension
|
|
@@ -311,7 +316,8 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
311
316
|
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
312
317
|
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
313
318
|
canvas_widget = self.canvas.get_tk_widget()
|
|
314
|
-
canvas_widget.pack(fill=tk.BOTH, expand=True)
|
|
319
|
+
# canvas_widget.pack(fill=tk.BOTH, expand=True) # This does not work in Fedora
|
|
320
|
+
canvas_widget.pack(expand=True)
|
|
315
321
|
|
|
316
322
|
# Matplotlib toolbar
|
|
317
323
|
self.toolbar_frame = tk.Frame(self.root)
|
|
@@ -357,41 +363,81 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
357
363
|
self.last_xmax = updated_params['xmax']['value']
|
|
358
364
|
self.last_ymin = updated_params['ymin']['value']
|
|
359
365
|
self.last_ymax = updated_params['ymax']['value']
|
|
366
|
+
usefulregion = SliceRegion2D(f"[{self.last_xmin}:{self.last_xmax},{self.last_ymin}:{self.last_ymax}]",
|
|
367
|
+
mode="fits").python
|
|
368
|
+
usefulmask = np.zeros_like(self.data)
|
|
369
|
+
usefulmask[usefulregion] = 1.0
|
|
360
370
|
# Update parameter dictionary with new values
|
|
361
371
|
self.lacosmic_params = updated_params
|
|
362
372
|
print("Parameters updated:")
|
|
363
373
|
for key, info in self.lacosmic_params.items():
|
|
364
374
|
print(f" {key}: {info['value']}")
|
|
375
|
+
if self.lacosmic_params['nruns']['value'] not in [1, 2]:
|
|
376
|
+
raise ValueError("nruns must be 1 or 2")
|
|
365
377
|
# Execute L.A.Cosmic with updated parameters
|
|
366
378
|
cleandata_lacosmic, mask_crfound = cosmicray_lacosmic(
|
|
367
379
|
self.data,
|
|
368
|
-
gain=self.lacosmic_params['
|
|
369
|
-
readnoise=self.lacosmic_params['
|
|
370
|
-
sigclip=self.lacosmic_params['
|
|
371
|
-
sigfrac=self.lacosmic_params['
|
|
372
|
-
objlim=self.lacosmic_params['
|
|
373
|
-
niter=self.lacosmic_params['
|
|
374
|
-
verbose=self.lacosmic_params['
|
|
380
|
+
gain=self.lacosmic_params['run1_gain']['value'],
|
|
381
|
+
readnoise=self.lacosmic_params['run1_readnoise']['value'],
|
|
382
|
+
sigclip=self.lacosmic_params['run1_sigclip']['value'],
|
|
383
|
+
sigfrac=self.lacosmic_params['run1_sigfrac']['value'],
|
|
384
|
+
objlim=self.lacosmic_params['run1_objlim']['value'],
|
|
385
|
+
niter=self.lacosmic_params['run1_niter']['value'],
|
|
386
|
+
verbose=self.lacosmic_params['run1_verbose']['value']
|
|
375
387
|
)
|
|
388
|
+
# Apply usefulmask to consider only selected region
|
|
389
|
+
cleandata_lacosmic *= usefulmask
|
|
390
|
+
mask_crfound = mask_crfound & (usefulmask.astype(bool))
|
|
391
|
+
# Second execution if nruns == 2
|
|
392
|
+
if self.lacosmic_params['nruns']['value'] == 2:
|
|
393
|
+
cleandata_lacosmic2, mask_crfound2 = cosmicray_lacosmic(
|
|
394
|
+
self.data,
|
|
395
|
+
gain=self.lacosmic_params['run2_gain']['value'],
|
|
396
|
+
readnoise=self.lacosmic_params['run2_readnoise']['value'],
|
|
397
|
+
sigclip=self.lacosmic_params['run2_sigclip']['value'],
|
|
398
|
+
sigfrac=self.lacosmic_params['run2_sigfrac']['value'],
|
|
399
|
+
objlim=self.lacosmic_params['run2_objlim']['value'],
|
|
400
|
+
niter=self.lacosmic_params['run2_niter']['value'],
|
|
401
|
+
verbose=self.lacosmic_params['run2_verbose']['value']
|
|
402
|
+
)
|
|
403
|
+
# Apply usefulmask to consider only selected region
|
|
404
|
+
cleandata_lacosmic2 *= usefulmask
|
|
405
|
+
mask_crfound2 = mask_crfound2 & (usefulmask.astype(bool))
|
|
406
|
+
# Combine results from both runs
|
|
407
|
+
if np.any(mask_crfound):
|
|
408
|
+
print(f"Number of cosmic ray pixels (run1).......: {np.sum(mask_crfound)}")
|
|
409
|
+
print(f"Number of cosmic ray pixels (run2).......: {np.sum(mask_crfound2)}")
|
|
410
|
+
# find features in second run
|
|
411
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
412
|
+
cr_labels2, num_features2 = ndimage.label(mask_crfound2, structure=structure)
|
|
413
|
+
# generate mask of ones at CR pixels found in first run
|
|
414
|
+
mask_peaks = np.zeros(mask_crfound.shape, dtype=float)
|
|
415
|
+
mask_peaks[mask_crfound] = 1.0
|
|
416
|
+
# preserve only those CR pixels in second run that are in the first run
|
|
417
|
+
cr_labels2_preserved = mask_peaks * cr_labels2
|
|
418
|
+
# generate new mask with preserved CR pixels from second run
|
|
419
|
+
mask_crfound = np.zeros_like(mask_crfound, dtype=bool)
|
|
420
|
+
for icr in np.unique(cr_labels2_preserved):
|
|
421
|
+
if icr > 0:
|
|
422
|
+
mask_crfound[cr_labels2 == icr] = True
|
|
423
|
+
print(f'Number of cosmic ray pixels (run1 & run2): {np.sum(mask_crfound)}')
|
|
424
|
+
# Use the cleandata from the second run
|
|
425
|
+
cleandata_lacosmic = cleandata_lacosmic2
|
|
376
426
|
# Select the image region to process
|
|
377
|
-
fits_region = f"[{updated_params['xmin']['value']}:{updated_params['xmax']['value']}"
|
|
378
|
-
fits_region += f",{updated_params['ymin']['value']}:{updated_params['ymax']['value']}]"
|
|
379
|
-
region = SliceRegion2D(fits_region, mode="fits").python
|
|
380
427
|
self.cleandata_lacosmic = self.data.copy()
|
|
381
|
-
self.cleandata_lacosmic[
|
|
428
|
+
self.cleandata_lacosmic[usefulregion] = cleandata_lacosmic[usefulregion]
|
|
382
429
|
self.mask_crfound = np.zeros_like(self.data, dtype=bool)
|
|
383
|
-
self.mask_crfound[
|
|
430
|
+
self.mask_crfound[usefulregion] = mask_crfound[usefulregion]
|
|
384
431
|
# Process the mask: dilation and labeling
|
|
385
432
|
if np.any(self.mask_crfound):
|
|
386
433
|
num_cr_pixels_before_dilation = np.sum(self.mask_crfound)
|
|
387
434
|
dilation = self.lacosmic_params['dilation']['value']
|
|
388
435
|
if dilation > 0:
|
|
389
436
|
# Dilate the mask by the specified number of pixels
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
self.
|
|
393
|
-
|
|
394
|
-
iterations=self.lacosmic_params['dilation']['value']
|
|
437
|
+
self.mask_crfound = dilatemask(
|
|
438
|
+
mask=self.mask_crfound,
|
|
439
|
+
iterations=self.lacosmic_params['dilation']['value'],
|
|
440
|
+
connectivity=1
|
|
395
441
|
)
|
|
396
442
|
num_cr_pixels_after_dilation = np.sum(self.mask_crfound)
|
|
397
443
|
sdum = str(num_cr_pixels_after_dilation)
|
|
@@ -407,7 +453,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
407
453
|
# diagonal connections too, so we define a 3x3 square.
|
|
408
454
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
409
455
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
410
|
-
print(f"Number of cosmic rays features (grouped pixels)...: {self.num_features
|
|
456
|
+
print(f"Number of cosmic rays features (grouped pixels)...: {self.num_features:>{len(sdum)}}")
|
|
411
457
|
self.apply_lacosmic_button.config(state=tk.NORMAL)
|
|
412
458
|
self.examine_detected_cr_button.config(state=tk.NORMAL)
|
|
413
459
|
self.update_cr_overlay()
|
|
@@ -452,10 +498,13 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
452
498
|
"""Apply the selected cleaning method to the detected cosmic rays."""
|
|
453
499
|
if np.any(self.mask_crfound):
|
|
454
500
|
# recalculate labels and number of features
|
|
455
|
-
structure = [[1, 1, 1],
|
|
501
|
+
structure = [[1, 1, 1],
|
|
502
|
+
[1, 1, 1],
|
|
503
|
+
[1, 1, 1]]
|
|
456
504
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
457
|
-
|
|
458
|
-
print(f"Number of cosmic
|
|
505
|
+
sdum = str(np.sum(self.mask_crfound))
|
|
506
|
+
print(f"Number of cosmic ray pixels detected by L.A.Cosmic...........: {sdum}")
|
|
507
|
+
print(f"Number of cosmic rays (grouped pixels) detected by L.A.Cosmic: {self.num_features:>{len(sdum)}}")
|
|
459
508
|
# Define parameters for L.A.Cosmic from default dictionary
|
|
460
509
|
editor_window = tk.Toplevel(self.root)
|
|
461
510
|
editor = InterpolationEditor(
|
|
@@ -463,7 +512,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
463
512
|
last_dilation=self.lacosmic_params['dilation']['value'],
|
|
464
513
|
last_npoints=self.last_npoints,
|
|
465
514
|
last_degree=self.last_degree,
|
|
466
|
-
auxdata=self.auxdata
|
|
515
|
+
auxdata=self.auxdata,
|
|
467
516
|
)
|
|
468
517
|
# Make it modal (blocks interaction with main window)
|
|
469
518
|
editor_window.transient(self.root)
|
|
@@ -498,7 +547,7 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
498
547
|
self.mask_crfound[self.mask_crfound] = False
|
|
499
548
|
num_cr_cleaned = self.num_features
|
|
500
549
|
else:
|
|
501
|
-
for i in range(1, self.num_features + 1):
|
|
550
|
+
for i in tqdm(range(1, self.num_features + 1)):
|
|
502
551
|
tmp_mask_fixed = np.zeros_like(self.data, dtype=bool)
|
|
503
552
|
if cleaning_method == 'x':
|
|
504
553
|
interpolation_performed, _, _ = interpolation_x(
|
|
@@ -536,6 +585,15 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
536
585
|
npoints=editor.npoints,
|
|
537
586
|
method='median'
|
|
538
587
|
)
|
|
588
|
+
elif cleaning_method == 'a-mean':
|
|
589
|
+
interpolation_performed, _, _ = interpolation_a(
|
|
590
|
+
data=self.data,
|
|
591
|
+
mask_fixed=tmp_mask_fixed,
|
|
592
|
+
cr_labels=self.cr_labels,
|
|
593
|
+
cr_index=i,
|
|
594
|
+
npoints=editor.npoints,
|
|
595
|
+
method='mean'
|
|
596
|
+
)
|
|
539
597
|
else:
|
|
540
598
|
raise ValueError(f"Unknown cleaning method: {cleaning_method}")
|
|
541
599
|
if interpolation_performed:
|
|
@@ -548,8 +606,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
548
606
|
# recalculate labels and number of features
|
|
549
607
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
550
608
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
551
|
-
|
|
552
|
-
print(f"Remaining number of cosmic
|
|
609
|
+
sdum = str(np.sum(self.mask_crfound))
|
|
610
|
+
print(f"Remaining number of cosmic ray pixels...........: {sdum}")
|
|
611
|
+
print(f"Remaining number of cosmic rays (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
553
612
|
# redraw image to show the changes
|
|
554
613
|
self.image.set_data(self.data)
|
|
555
614
|
self.canvas.draw_idle()
|
|
@@ -612,8 +671,9 @@ class CosmicRayCleanerApp(ImageDisplay):
|
|
|
612
671
|
# recalculate labels and number of features
|
|
613
672
|
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
614
673
|
self.cr_labels, self.num_features = ndimage.label(self.mask_crfound, structure=structure)
|
|
615
|
-
|
|
616
|
-
print(f"Remaining number of cosmic
|
|
674
|
+
sdum = str(np.sum(self.mask_crfound))
|
|
675
|
+
print(f"Remaining number of cosmic ray pixels...........: {sdum}")
|
|
676
|
+
print(f"Remaining number of cosmic rays (grouped pixels): {self.num_features:>{len(sdum)}}")
|
|
617
677
|
# redraw image to show the changes
|
|
618
678
|
self.image.set_data(self.data)
|
|
619
679
|
self.canvas.draw_idle()
|
|
@@ -14,21 +14,31 @@
|
|
|
14
14
|
# using the intrinsic Python types, so that they can be easily cast
|
|
15
15
|
# when reading user input.
|
|
16
16
|
lacosmic_default_dict = {
|
|
17
|
-
# L.A.Cosmic parameters
|
|
18
|
-
'
|
|
19
|
-
'
|
|
20
|
-
'
|
|
21
|
-
'
|
|
22
|
-
'
|
|
23
|
-
'
|
|
24
|
-
'
|
|
17
|
+
# L.A.Cosmic parameters for run 1
|
|
18
|
+
'run1_gain': {'value': 1.0, 'type': float, 'positive': True},
|
|
19
|
+
'run1_readnoise': {'value': 6.5, 'type': float, 'positive': True},
|
|
20
|
+
'run1_sigclip': {'value': 5.0, 'type': float, 'positive': True},
|
|
21
|
+
'run1_sigfrac': {'value': 0.3, 'type': float, 'positive': True},
|
|
22
|
+
'run1_objlim': {'value': 5.0, 'type': float, 'positive': True},
|
|
23
|
+
'run1_niter': {'value': 4, 'type': int, 'positive': True},
|
|
24
|
+
'run1_verbose': {'value': False, 'type': bool},
|
|
25
|
+
# L.A.Cosmic parameters for run 2
|
|
26
|
+
'run2_gain': {'value': 1.0, 'type': float, 'positive': True},
|
|
27
|
+
'run2_readnoise': {'value': 6.5, 'type': float, 'positive': True},
|
|
28
|
+
'run2_sigclip': {'value': 3.0, 'type': float, 'positive': True},
|
|
29
|
+
'run2_sigfrac': {'value': 0.3, 'type': float, 'positive': True},
|
|
30
|
+
'run2_objlim': {'value': 5.0, 'type': float, 'positive': True},
|
|
31
|
+
'run2_niter': {'value': 4, 'type': int, 'positive': True},
|
|
32
|
+
'run2_verbose': {'value': False, 'type': bool},
|
|
25
33
|
# Dilation of the mask
|
|
26
34
|
'dilation': {'value': 0, 'type': int, 'positive': True},
|
|
27
35
|
# Limits for the image section to process (pixels start at 1)
|
|
28
36
|
'xmin': {'value': 1, 'type': int, 'positive': True},
|
|
29
37
|
'xmax': {'value': None, 'type': int, 'positive': True},
|
|
30
38
|
'ymin': {'value': 1, 'type': int, 'positive': True},
|
|
31
|
-
'ymax': {'value': None, 'type': int, 'positive': True}
|
|
39
|
+
'ymax': {'value': None, 'type': int, 'positive': True},
|
|
40
|
+
# Number of runs to execute L.A.Cosmic
|
|
41
|
+
'nruns': {'value': 1, 'type': int, 'positive': True}
|
|
32
42
|
}
|
|
33
43
|
|
|
34
44
|
# Default parameters for cleaning methods
|
|
@@ -37,6 +47,7 @@ VALID_CLEANING_METHODS = [
|
|
|
37
47
|
'y interp.',
|
|
38
48
|
'surface interp.',
|
|
39
49
|
'median',
|
|
50
|
+
'mean',
|
|
40
51
|
'lacosmic',
|
|
41
52
|
'auxdata'
|
|
42
53
|
]
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0+
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Dilate cosmic ray mask"""
|
|
11
|
+
|
|
12
|
+
from scipy import ndimage
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def dilatemask(mask, iterations, rank=None, connectivity=1):
|
|
16
|
+
"""Dilate mask by a given number of points.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
mask : numpy.ndarray of bool
|
|
21
|
+
A boolean mask array indicating cosmic ray affected pixels.
|
|
22
|
+
iterations : int
|
|
23
|
+
The number of dilation iterations to perform. Each iteration
|
|
24
|
+
expands the mask by one pixel in the specified connectivity.
|
|
25
|
+
rank : int, optional
|
|
26
|
+
The rank of the array. If None, it is inferred from crmask.
|
|
27
|
+
See scipy.ndimage.generate_binary_structure for details.
|
|
28
|
+
connectivity : int, optional
|
|
29
|
+
The connectivity for the structuring element. Default is 1.
|
|
30
|
+
See scipy.ndimage.generate_binary_structure for details.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
dilated_mask : numpy.ndarray of bool
|
|
35
|
+
The dilated mask.
|
|
36
|
+
"""
|
|
37
|
+
if rank is None:
|
|
38
|
+
rank = mask.ndim
|
|
39
|
+
structure = ndimage.generate_binary_structure(rank, connectivity)
|
|
40
|
+
dilated_mask = ndimage.binary_dilation(mask, structure=structure, iterations=iterations)
|
|
41
|
+
return dilated_mask
|
|
@@ -10,11 +10,12 @@
|
|
|
10
10
|
"""Surface interpolation (plane fit) or median interpolation"""
|
|
11
11
|
|
|
12
12
|
import numpy as np
|
|
13
|
-
|
|
13
|
+
|
|
14
|
+
from .dilatemask import dilatemask
|
|
14
15
|
|
|
15
16
|
|
|
16
17
|
def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
17
|
-
"""
|
|
18
|
+
"""Fix cosmic ray pixels using surface fit, median or mean.
|
|
18
19
|
|
|
19
20
|
Parameters
|
|
20
21
|
----------
|
|
@@ -29,7 +30,7 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
29
30
|
npoints : int
|
|
30
31
|
The number of points to use for interpolation.
|
|
31
32
|
method : str
|
|
32
|
-
The interpolation method to use ('surface' or '
|
|
33
|
+
The interpolation method to use ('surface', 'median' or 'mean').
|
|
33
34
|
|
|
34
35
|
Returns
|
|
35
36
|
-------
|
|
@@ -39,11 +40,27 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
39
40
|
X-coordinates of border pixels used for interpolation.
|
|
40
41
|
yfit_all : list
|
|
41
42
|
Y-coordinates of border pixels used for interpolation.
|
|
43
|
+
|
|
44
|
+
Notes
|
|
45
|
+
-----
|
|
46
|
+
The `data` array is modified in place with interpolated values for the
|
|
47
|
+
cosmic ray pixels. This function also returns an updated `mask_fixed`
|
|
48
|
+
array with interpolated pixels marked as fixed.
|
|
49
|
+
|
|
50
|
+
It is important to highlight that contrary to what is performed when
|
|
51
|
+
using the X- and Y-interpolation, this function does not fill the gaps
|
|
52
|
+
between the marked pixels. Only the pixels explicitly marked as affected
|
|
53
|
+
by cosmic rays are interpolated.
|
|
42
54
|
"""
|
|
43
55
|
# Mask of CR pixels
|
|
44
56
|
mask = (cr_labels == cr_index)
|
|
45
57
|
# Dilate the mask to find border pixels
|
|
46
|
-
dilated_mask = binary_dilation(mask, structure=np.ones((3, 3)), iterations=npoints)
|
|
58
|
+
# dilated_mask = binary_dilation(mask, structure=np.ones((3, 3)), iterations=npoints)
|
|
59
|
+
dilated_mask = dilatemask(
|
|
60
|
+
mask=mask,
|
|
61
|
+
iterations=npoints,
|
|
62
|
+
connectivity=1
|
|
63
|
+
)
|
|
47
64
|
# Border pixels are those in the dilated mask but not in the original mask
|
|
48
65
|
border_mask = dilated_mask & (~mask)
|
|
49
66
|
# Get coordinates of border pixels
|
|
@@ -67,16 +84,21 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
67
84
|
interpolation_performed = True
|
|
68
85
|
else:
|
|
69
86
|
print("Not enough points to fit a plane")
|
|
70
|
-
elif method
|
|
87
|
+
elif method in ['median', 'mean']:
|
|
71
88
|
# Compute median of all surrounding points
|
|
72
89
|
if len(zfit_all) > 0:
|
|
73
|
-
|
|
90
|
+
if method == 'median':
|
|
91
|
+
zval = np.median(zfit_all)
|
|
92
|
+
else:
|
|
93
|
+
zval = np.mean(zfit_all)
|
|
74
94
|
# recompute all CR pixels to take into account "holes" between marked pixels
|
|
75
95
|
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
76
96
|
for iy, ix in zip(ycr_list, xcr_list):
|
|
77
|
-
data[iy, ix] =
|
|
97
|
+
data[iy, ix] = zval
|
|
78
98
|
mask_fixed[iy, ix] = True
|
|
79
99
|
interpolation_performed = True
|
|
100
|
+
else:
|
|
101
|
+
print("No surrounding points found for median interpolation")
|
|
80
102
|
else:
|
|
81
103
|
print(f"Unknown interpolation method: {method}")
|
|
82
104
|
|
|
@@ -37,6 +37,24 @@ def interpolation_x(data, mask_fixed, cr_labels, cr_index, npoints, degree):
|
|
|
37
37
|
X-coordinates of border pixels used for interpolation.
|
|
38
38
|
yfit_all : list
|
|
39
39
|
Y-coordinates of border pixels used for interpolation.
|
|
40
|
+
|
|
41
|
+
Notes
|
|
42
|
+
-----
|
|
43
|
+
The `data` array is modified in place with interpolated values for the
|
|
44
|
+
cosmic ray pixels. This function also returns an updated `mask_fixed`
|
|
45
|
+
array with interpolated pixels marked as fixed.
|
|
46
|
+
|
|
47
|
+
It is important to highlight that this function assumes that at
|
|
48
|
+
every y-coordinate where cosmic ray pixels are found, the pixels
|
|
49
|
+
form a contiguous horizontal segment. In this sense, gaps in the
|
|
50
|
+
x-direction are assumed to be also part of the same cosmic ray
|
|
51
|
+
feature, and the `cr_labels` array is updated accordingly. In other
|
|
52
|
+
words, all the pixels between the minimum and maximum x-coordinates
|
|
53
|
+
of the cosmic ray pixels at a given y-coordinate are treated as
|
|
54
|
+
affected by the cosmic ray. This simplyfies the interactive marking
|
|
55
|
+
of cosmic rays, as the user does not need to ensure that all pixels
|
|
56
|
+
in a horizontal segment are marked; marking just the extreme pixels
|
|
57
|
+
is sufficient.
|
|
40
58
|
"""
|
|
41
59
|
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
42
60
|
ycr_min = np.min(ycr_list)
|
|
@@ -37,6 +37,24 @@ def interpolation_y(data, mask_fixed, cr_labels, cr_index, npoints, degree):
|
|
|
37
37
|
X-coordinates of border pixels used for interpolation.
|
|
38
38
|
yfit_all : list
|
|
39
39
|
Y-coordinates of border pixels used for interpolation.
|
|
40
|
+
|
|
41
|
+
Notes
|
|
42
|
+
-----
|
|
43
|
+
The `data` array is modified in place with interpolated values for the
|
|
44
|
+
cosmic ray pixels. This function also returns an updated `mask_fixed`
|
|
45
|
+
array with interpolated pixels marked as fixed.
|
|
46
|
+
|
|
47
|
+
It is important to highlight that this function assumes that at
|
|
48
|
+
every x-coordinate where cosmic ray pixels are found, the pixels
|
|
49
|
+
form a contiguous vertical segment. In this sense, gaps in the
|
|
50
|
+
y-direction are assumed to be also part of the same cosmic ray
|
|
51
|
+
feature, and the `cr_labels` array is updated accordingly. In other
|
|
52
|
+
words, all the pixels between the minimum and maximum y-coordinates
|
|
53
|
+
of the cosmic ray pixels at a given x-coordinate are treated as
|
|
54
|
+
affected by the cosmic ray. This simplyfies the interactive marking
|
|
55
|
+
of cosmic rays, as the user does not need to ensure that all pixels
|
|
56
|
+
in a vertical segment are marked; marking just the extreme pixels
|
|
57
|
+
is sufficient.
|
|
40
58
|
"""
|
|
41
59
|
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
42
60
|
xcr_min = np.min(xcr_list)
|
|
@@ -72,6 +72,7 @@ class InterpolationEditor:
|
|
|
72
72
|
"y interp.": "y",
|
|
73
73
|
"surface interp.": "a-plane",
|
|
74
74
|
"median": "a-median",
|
|
75
|
+
"mean": "a-mean",
|
|
75
76
|
"lacosmic": "lacosmic",
|
|
76
77
|
"auxdata": "auxdata"
|
|
77
78
|
}
|
|
@@ -190,6 +191,9 @@ class InterpolationEditor:
|
|
|
190
191
|
elif selected_method == 'median':
|
|
191
192
|
self.entry_npoints.config(state='normal')
|
|
192
193
|
self.entry_degree.config(state='disabled')
|
|
194
|
+
elif selected_method == 'mean':
|
|
195
|
+
self.entry_npoints.config(state='normal')
|
|
196
|
+
self.entry_degree.config(state='disabled')
|
|
193
197
|
elif selected_method == 'lacosmic':
|
|
194
198
|
self.entry_npoints.config(state='disabled')
|
|
195
199
|
self.entry_degree.config(state='disabled')
|
|
@@ -75,7 +75,7 @@ class ParameterEditor:
|
|
|
75
75
|
self.param_dict['ymin']['value'] = ymin
|
|
76
76
|
self.param_dict['ymax']['value'] = ymax
|
|
77
77
|
self.imgshape = imgshape
|
|
78
|
-
self.entries = {} # dictionary to hold entry widgets
|
|
78
|
+
self.entries = {'run1': {}, 'run2': {}} # dictionary to hold entry widgets
|
|
79
79
|
self.result_dict = None
|
|
80
80
|
|
|
81
81
|
# Create the form
|
|
@@ -91,33 +91,48 @@ class ParameterEditor:
|
|
|
91
91
|
|
|
92
92
|
# Subtitle for L.A.Cosmic parameters
|
|
93
93
|
subtitle_label = tk.Label(main_frame, text="L.A.Cosmic Parameters", font=("Arial", 14, "bold"))
|
|
94
|
-
subtitle_label.grid(row=row, column=0, columnspan=
|
|
94
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
|
|
95
95
|
row += 1
|
|
96
96
|
|
|
97
|
-
# Create labels and entry fields for each parameter
|
|
97
|
+
# Create labels and entry fields for each parameter.
|
|
98
|
+
# Note: here we are using entry_vars to trace changes in the entries
|
|
99
|
+
# so that we can update the color of run2 entries if they differ from run1.
|
|
100
|
+
self.entry_vars = {}
|
|
98
101
|
for key, info in self.param_dict.items():
|
|
99
|
-
if key.
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
102
|
+
if not key.startswith('run1_'):
|
|
103
|
+
continue
|
|
104
|
+
# Parameter name label
|
|
105
|
+
label = tk.Label(main_frame, text=f"{key[5:]}:", anchor='e', width=15)
|
|
106
|
+
label.grid(row=row, column=0, sticky='w', pady=5)
|
|
107
|
+
# Entry field for run1
|
|
108
|
+
self.entry_vars[key] = tk.StringVar()
|
|
109
|
+
self.entry_vars[key].trace_add('write', lambda *args: self.update_colour_param_run1_run2())
|
|
110
|
+
entry = tk.Entry(main_frame, textvariable=self.entry_vars[key], width=10)
|
|
111
|
+
entry.insert(0, str(info['value']))
|
|
112
|
+
entry.grid(row=row, column=1, padx=10, pady=5)
|
|
113
|
+
self.entries[key] = entry # dictionary to hold entry widgets
|
|
114
|
+
# Entry field for run2
|
|
115
|
+
key2 = 'run2_' + key[5:]
|
|
116
|
+
self.entry_vars[key2] = tk.StringVar()
|
|
117
|
+
self.entry_vars[key2].trace_add('write', lambda *args: self.update_colour_param_run1_run2())
|
|
118
|
+
entry = tk.Entry(main_frame, textvariable=self.entry_vars[key2], width=10)
|
|
119
|
+
entry.insert(0, str(self.param_dict[key2]['value']))
|
|
120
|
+
entry.grid(row=row, column=2, padx=10, pady=5)
|
|
121
|
+
self.entries['run2_'+key[5:]] = entry # dictionary to hold entry widgets
|
|
122
|
+
# Type label
|
|
123
|
+
type_label = tk.Label(main_frame, text=f"({info['type'].__name__})", fg='gray', anchor='w', width=10)
|
|
124
|
+
type_label.grid(row=row, column=3, sticky='w', pady=5)
|
|
125
|
+
row += 1
|
|
126
|
+
# self.update_colour_param_run1_run2()
|
|
112
127
|
|
|
113
128
|
# Separator
|
|
114
129
|
separator1 = ttk.Separator(main_frame, orient='horizontal')
|
|
115
|
-
separator1.grid(row=row, column=0, columnspan=
|
|
130
|
+
separator1.grid(row=row, column=0, columnspan=4, sticky='ew', pady=(10, 10))
|
|
116
131
|
row += 1
|
|
117
132
|
|
|
118
133
|
# Subtitle for additional parameters
|
|
119
134
|
subtitle_label = tk.Label(main_frame, text="Additional Parameters", font=("Arial", 14, "bold"))
|
|
120
|
-
subtitle_label.grid(row=row, column=0, columnspan=
|
|
135
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
|
|
121
136
|
row += 1
|
|
122
137
|
|
|
123
138
|
# Dilation label and entry
|
|
@@ -134,12 +149,12 @@ class ParameterEditor:
|
|
|
134
149
|
|
|
135
150
|
# Separator
|
|
136
151
|
separator2 = ttk.Separator(main_frame, orient='horizontal')
|
|
137
|
-
separator2.grid(row=row, column=0, columnspan=
|
|
152
|
+
separator2.grid(row=row, column=0, columnspan=4, sticky='ew', pady=(10, 10))
|
|
138
153
|
row += 1
|
|
139
154
|
|
|
140
155
|
# Subtitle for region to be examined
|
|
141
156
|
subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=("Arial", 14, "bold"))
|
|
142
|
-
subtitle_label.grid(row=row, column=0, columnspan=
|
|
157
|
+
subtitle_label.grid(row=row, column=0, columnspan=4, pady=(0, 15))
|
|
143
158
|
row += 1
|
|
144
159
|
|
|
145
160
|
# Region to be examined label and entries
|
|
@@ -165,12 +180,12 @@ class ParameterEditor:
|
|
|
165
180
|
|
|
166
181
|
# Separator
|
|
167
182
|
separator3 = ttk.Separator(main_frame, orient='horizontal')
|
|
168
|
-
separator3.grid(row=row, column=0, columnspan=
|
|
183
|
+
separator3.grid(row=row, column=0, columnspan=4, sticky='ew', pady=(10, 10))
|
|
169
184
|
row += 1
|
|
170
185
|
|
|
171
186
|
# Button frame
|
|
172
187
|
button_frame = tk.Frame(main_frame)
|
|
173
|
-
button_frame.grid(row=row, column=0, columnspan=
|
|
188
|
+
button_frame.grid(row=row, column=0, columnspan=4, pady=(5, 0))
|
|
174
189
|
|
|
175
190
|
# OK button
|
|
176
191
|
ok_button = tk.Button(button_frame, text="OK", width=5, command=self.on_ok)
|
|
@@ -190,6 +205,8 @@ class ParameterEditor:
|
|
|
190
205
|
updated_dict = {}
|
|
191
206
|
|
|
192
207
|
for key, info in self.param_dict.items():
|
|
208
|
+
if key == 'nruns':
|
|
209
|
+
continue
|
|
193
210
|
entry_value = self.entries[key].get()
|
|
194
211
|
value_type = info['type']
|
|
195
212
|
|
|
@@ -212,6 +229,17 @@ class ParameterEditor:
|
|
|
212
229
|
'type': value_type
|
|
213
230
|
}
|
|
214
231
|
|
|
232
|
+
# Check whether any run1 and run2 parameters differ
|
|
233
|
+
nruns = 1
|
|
234
|
+
for key in self.param_dict.keys():
|
|
235
|
+
if key.startswith('run1_'):
|
|
236
|
+
parname = key[5:]
|
|
237
|
+
key2 = 'run2_' + parname
|
|
238
|
+
if updated_dict[key]['value'] != updated_dict[key2]['value']:
|
|
239
|
+
nruns = 2
|
|
240
|
+
print(f"Parameter '{parname}' differs between run1 and run2: "
|
|
241
|
+
f"{updated_dict[key]['value']} (run1) vs {updated_dict[key2]['value']} (run2)")
|
|
242
|
+
|
|
215
243
|
# Additional validation for region limits
|
|
216
244
|
try:
|
|
217
245
|
if updated_dict['xmax']['value'] <= updated_dict['xmin']['value']:
|
|
@@ -219,6 +247,9 @@ class ParameterEditor:
|
|
|
219
247
|
if updated_dict['ymax']['value'] <= updated_dict['ymin']['value']:
|
|
220
248
|
raise ValueError("ymax must be greater than ymin")
|
|
221
249
|
self.result_dict = updated_dict
|
|
250
|
+
self.result_dict['nruns'] = {'value': nruns, 'type': int, 'positive': True}
|
|
251
|
+
if nruns not in [1, 2]:
|
|
252
|
+
raise ValueError("nruns must be 1 or 2")
|
|
222
253
|
self.root.destroy()
|
|
223
254
|
except ValueError as e:
|
|
224
255
|
messagebox.showerror("Invalid Inputs",
|
|
@@ -243,9 +274,23 @@ class ParameterEditor:
|
|
|
243
274
|
self.param_dict['ymin']['value'] = 1
|
|
244
275
|
self.param_dict['ymax']['value'] = self.imgshape[0]
|
|
245
276
|
for key, info in self.param_dict.items():
|
|
277
|
+
if key == 'nruns':
|
|
278
|
+
continue
|
|
246
279
|
self.entries[key].delete(0, tk.END)
|
|
247
280
|
self.entries[key].insert(0, str(info['value']))
|
|
248
281
|
|
|
249
282
|
def get_result(self):
|
|
250
283
|
"""Return the updated dictionary"""
|
|
251
284
|
return self.result_dict
|
|
285
|
+
|
|
286
|
+
def update_colour_param_run1_run2(self):
|
|
287
|
+
"""Update the foreground color of run1 and run2 entries."""
|
|
288
|
+
# Highlight run2 parameter if different from run1
|
|
289
|
+
for key in self.param_dict.keys():
|
|
290
|
+
if key.startswith('run1_'):
|
|
291
|
+
parname = key[5:]
|
|
292
|
+
if key in self.entries and 'run2_'+parname in self.entries:
|
|
293
|
+
if self.entries[key].get() != self.entries['run2_'+parname].get():
|
|
294
|
+
self.entries['run2_'+parname].config(fg='red')
|
|
295
|
+
else:
|
|
296
|
+
self.entries['run2_'+parname].config(fg='black')
|
|
@@ -133,9 +133,13 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
133
133
|
self.root.title("Review Cosmic Rays")
|
|
134
134
|
self.auxdata = auxdata
|
|
135
135
|
if self.auxdata is not None:
|
|
136
|
-
self.root.geometry("
|
|
136
|
+
# self.root.geometry("1000x700+100+100") # This does not work in Fedora
|
|
137
|
+
self.root.minsize(1000, 700)
|
|
137
138
|
else:
|
|
138
|
-
self.root.geometry("800x700+100+100")
|
|
139
|
+
# self.root.geometry("800x700+100+100") # This does not work in Fedora
|
|
140
|
+
self.root.minsize(800, 700)
|
|
141
|
+
self.root.update_idletasks()
|
|
142
|
+
self.root.geometry("+100+100")
|
|
139
143
|
self.data = data
|
|
140
144
|
self.cleandata_lacosmic = cleandata_lacosmic
|
|
141
145
|
self.data_original = data.copy()
|
|
@@ -193,8 +197,11 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
193
197
|
self.interp_s_button = tk.Button(self.button_frame2, text="[s]urface interp.",
|
|
194
198
|
command=lambda: self.interp_a('surface'))
|
|
195
199
|
self.interp_s_button.pack(side=tk.LEFT, padx=5)
|
|
196
|
-
self.
|
|
200
|
+
self.interp_d_button = tk.Button(self.button_frame2, text="me[d]ian",
|
|
197
201
|
command=lambda: self.interp_a('median'))
|
|
202
|
+
self.interp_d_button.pack(side=tk.LEFT, padx=5)
|
|
203
|
+
self.interp_m_button = tk.Button(self.button_frame2, text="[m]ean",
|
|
204
|
+
command=lambda: self.interp_a('mean'))
|
|
198
205
|
self.interp_m_button.pack(side=tk.LEFT, padx=5)
|
|
199
206
|
self.interp_l_button = tk.Button(self.button_frame2, text="[l]acosmic", command=self.use_lacosmic)
|
|
200
207
|
self.interp_l_button.pack(side=tk.LEFT, padx=5)
|
|
@@ -223,9 +230,9 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
223
230
|
# Figure
|
|
224
231
|
if self.auxdata is not None:
|
|
225
232
|
self.fig, (self.ax_aux, self.ax) = plt.subplots(
|
|
226
|
-
ncols=2, figsize=(
|
|
233
|
+
ncols=2, figsize=(11, 5.5), constrained_layout=True)
|
|
227
234
|
else:
|
|
228
|
-
self.fig, self.ax = plt.subplots(figsize=(8, 5), constrained_layout=True)
|
|
235
|
+
self.fig, self.ax = plt.subplots(figsize=(8, 5.5), constrained_layout=True)
|
|
229
236
|
self.canvas = FigureCanvasTkAgg(self.fig, master=self.root)
|
|
230
237
|
self.canvas.get_tk_widget().pack(padx=5, pady=5)
|
|
231
238
|
# The next two instructions prevent a segmentation fault when pressing "q"
|
|
@@ -233,7 +240,8 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
233
240
|
self.canvas.mpl_connect("key_press_event", self.on_key)
|
|
234
241
|
self.canvas.mpl_connect("button_press_event", self.on_click)
|
|
235
242
|
self.canvas_widget = self.canvas.get_tk_widget()
|
|
236
|
-
self.canvas_widget.pack(fill=tk.BOTH, expand=True)
|
|
243
|
+
# self.canvas_widget.pack(fill=tk.BOTH, expand=True) # This does not work in Fedora
|
|
244
|
+
self.canvas_widget.pack(expand=True)
|
|
237
245
|
|
|
238
246
|
# Matplotlib toolbar
|
|
239
247
|
self.toolbar_frame = tk.Frame(self.root)
|
|
@@ -262,20 +270,24 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
262
270
|
# to avoid image shifts when some pixels are unmarked or new ones are marked
|
|
263
271
|
i0 = int(np.mean(ycr_list_original) + 0.5)
|
|
264
272
|
j0 = int(np.mean(xcr_list_original) + 0.5)
|
|
265
|
-
|
|
273
|
+
max_distance_from_center = np.max(
|
|
274
|
+
[np.max(np.abs(ycr_list_original - i0)),
|
|
275
|
+
np.max(np.abs(xcr_list_original - j0))]
|
|
276
|
+
)
|
|
277
|
+
semiwidth = int(np.max([max_distance_from_center, MAX_PIXEL_DISTANCE_TO_CR]))
|
|
266
278
|
jmin = j0 - semiwidth if j0 - semiwidth >= 0 else 0
|
|
267
279
|
jmax = j0 + semiwidth if j0 + semiwidth < self.data.shape[1] else self.data.shape[1] - 1
|
|
268
280
|
imin = i0 - semiwidth if i0 - semiwidth >= 0 else 0
|
|
269
281
|
imax = i0 + semiwidth if i0 + semiwidth < self.data.shape[0] else self.data.shape[0] - 1
|
|
270
282
|
# Force the region to be of size (2*semiwidth + 1) x (2*semiwidth + 1)
|
|
271
283
|
if jmin == 0:
|
|
272
|
-
jmax = 2 * semiwidth
|
|
284
|
+
jmax = np.min([2 * semiwidth, self.data.shape[1] - 1])
|
|
273
285
|
elif jmax == self.data.shape[1] - 1:
|
|
274
|
-
jmin = self.data.shape[1] - 1 - 2 * semiwidth
|
|
286
|
+
jmin = np.max([0, self.data.shape[1] - 1 - 2 * semiwidth])
|
|
275
287
|
if imin == 0:
|
|
276
|
-
imax = 2 * semiwidth
|
|
288
|
+
imax = np.min([2 * semiwidth, self.data.shape[0] - 1])
|
|
277
289
|
elif imax == self.data.shape[0] - 1:
|
|
278
|
-
imin = self.data.shape[0] - 1 - 2 * semiwidth
|
|
290
|
+
imin = np.max([0, self.data.shape[0] - 1 - 2 * semiwidth])
|
|
279
291
|
self.region = SliceRegion2D(f'[{jmin+1}:{jmax+1}, {imin+1}:{imax+1}]', mode='fits').python
|
|
280
292
|
self.ax.clear()
|
|
281
293
|
vmin = self.get_vmin()
|
|
@@ -333,6 +345,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
333
345
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
334
346
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
335
347
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
348
|
+
self.interp_d_button.config(state=tk.DISABLED)
|
|
336
349
|
self.interp_m_button.config(state=tk.DISABLED)
|
|
337
350
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
338
351
|
self.interp_aux_button.config(state=tk.DISABLED)
|
|
@@ -387,7 +400,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
387
400
|
Parameters
|
|
388
401
|
----------
|
|
389
402
|
method : str
|
|
390
|
-
The interpolation method to use ('surface' or '
|
|
403
|
+
The interpolation method to use ('surface', 'median' or 'mean').
|
|
391
404
|
"""
|
|
392
405
|
print(f"{method} interpolation of cosmic ray {self.cr_index}")
|
|
393
406
|
interpolation_performed, xfit_all, yfit_all = interpolation_a(
|
|
@@ -444,6 +457,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
444
457
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
445
458
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
446
459
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
460
|
+
self.interp_d_button.config(state=tk.DISABLED)
|
|
447
461
|
self.interp_m_button.config(state=tk.DISABLED)
|
|
448
462
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
449
463
|
self.interp_aux_button.config(state=tk.DISABLED)
|
|
@@ -458,6 +472,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
458
472
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
459
473
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
460
474
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
475
|
+
self.interp_d_button.config(state=tk.NORMAL)
|
|
461
476
|
self.interp_m_button.config(state=tk.NORMAL)
|
|
462
477
|
if self.cleandata_lacosmic is not None:
|
|
463
478
|
if self.last_dilation is None or self.last_dilation == 0:
|
|
@@ -484,6 +499,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
484
499
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
485
500
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
486
501
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
502
|
+
self.interp_d_button.config(state=tk.NORMAL)
|
|
487
503
|
self.interp_m_button.config(state=tk.NORMAL)
|
|
488
504
|
if self.cleandata_lacosmic is not None:
|
|
489
505
|
if self.last_dilation is None or self.last_dilation == 0:
|
|
@@ -513,9 +529,12 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
513
529
|
elif event.key == 's':
|
|
514
530
|
if self.interp_s_button.cget("state") != "disabled":
|
|
515
531
|
self.interp_a('surface')
|
|
532
|
+
elif event.key == 'd':
|
|
533
|
+
if self.interp_d_button.cget("state") != "disabled":
|
|
534
|
+
self.interp_a('median')
|
|
516
535
|
elif event.key == 'm':
|
|
517
536
|
if self.interp_m_button.cget("state") != "disabled":
|
|
518
|
-
self.interp_a('
|
|
537
|
+
self.interp_a('mean')
|
|
519
538
|
elif event.key == 'l':
|
|
520
539
|
if self.interp_l_button.cget("state") != "disabled":
|
|
521
540
|
self.use_lacosmic()
|
|
@@ -551,6 +570,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
551
570
|
self.interp_x_button.config(state=tk.DISABLED)
|
|
552
571
|
self.interp_y_button.config(state=tk.DISABLED)
|
|
553
572
|
self.interp_s_button.config(state=tk.DISABLED)
|
|
573
|
+
self.interp_d_button.config(state=tk.DISABLED)
|
|
554
574
|
self.interp_m_button.config(state=tk.DISABLED)
|
|
555
575
|
self.interp_l_button.config(state=tk.DISABLED)
|
|
556
576
|
self.interp_aux_button.config(state=tk.DISABLED)
|
|
@@ -559,6 +579,7 @@ class ReviewCosmicRay(ImageDisplay):
|
|
|
559
579
|
self.interp_x_button.config(state=tk.NORMAL)
|
|
560
580
|
self.interp_y_button.config(state=tk.NORMAL)
|
|
561
581
|
self.interp_s_button.config(state=tk.NORMAL)
|
|
582
|
+
self.interp_d_button.config(state=tk.NORMAL)
|
|
562
583
|
self.interp_m_button.config(state=tk.NORMAL)
|
|
563
584
|
if self.cleandata_lacosmic is not None:
|
|
564
585
|
if self.last_dilation is None or self.last_dilation == 0:
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
4
|
+
#
|
|
5
|
+
# This file is part of teareduce
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
8
|
+
# License-Filename: LICENSE.txt
|
|
9
|
+
#
|
|
10
|
+
"""Tests for the cleanest module."""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
from ..cleanest.cleanest import cleanest
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def test_cleanest_no_cr():
|
|
19
|
+
"""Test cleanest function with no cosmic rays."""
|
|
20
|
+
data = np.array([[1, 2, 3],
|
|
21
|
+
[4, 5, 6],
|
|
22
|
+
[7, 8, 9]], dtype=float)
|
|
23
|
+
mask_crfound = np.zeros_like(data, dtype=bool)
|
|
24
|
+
|
|
25
|
+
for interp_method in ['x', 'y', 's', 'd', 'm']:
|
|
26
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound, interp_method=interp_method, npoints=2, degree=1)
|
|
27
|
+
|
|
28
|
+
assert np.array_equal(cleaned_data, data), "Data should remain unchanged when no cosmic rays are present."
|
|
29
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged when no cosmic rays are present."
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def test_cleanest_interpolation_x():
|
|
33
|
+
"""Test cleanest function with interpolation in X direction."""
|
|
34
|
+
data = np.array([[1, 1, 2, 1, 1],
|
|
35
|
+
[2, 2, 3, 2, 2],
|
|
36
|
+
[3, 3, 4, 3, 3]], dtype=float)
|
|
37
|
+
mask_crfound = np.array([[False, False, True, False, False],
|
|
38
|
+
[False, False, True, False, False],
|
|
39
|
+
[False, False, True, False, False]], dtype=bool)
|
|
40
|
+
|
|
41
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound,
|
|
42
|
+
interp_method='x', npoints=2, degree=1)
|
|
43
|
+
|
|
44
|
+
expected_data = np.array([[1, 1, 1, 1, 1],
|
|
45
|
+
[2, 2, 2, 2, 2],
|
|
46
|
+
[3, 3, 3, 3, 3]], dtype=float)
|
|
47
|
+
|
|
48
|
+
assert np.allclose(cleaned_data, expected_data), "Interpolation in X direction failed."
|
|
49
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged after interpolation."
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def test_cleanest_interpolation_y():
|
|
53
|
+
"""Test cleanest function with interpolation in Y direction."""
|
|
54
|
+
data = np.array([[1, 2, 3],
|
|
55
|
+
[1, 2, 3],
|
|
56
|
+
[2, 3, 4],
|
|
57
|
+
[1, 2, 3],
|
|
58
|
+
[1, 2, 3]], dtype=float)
|
|
59
|
+
mask_crfound = np.array([[False, False, False],
|
|
60
|
+
[False, False, False],
|
|
61
|
+
[True, True, True],
|
|
62
|
+
[False, False, False],
|
|
63
|
+
[False, False, False]], dtype=bool)
|
|
64
|
+
|
|
65
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound,
|
|
66
|
+
interp_method='y', npoints=2, degree=1)
|
|
67
|
+
|
|
68
|
+
expected_data = np.array([[1, 2, 3],
|
|
69
|
+
[1, 2, 3],
|
|
70
|
+
[1, 2, 3],
|
|
71
|
+
[1, 2, 3],
|
|
72
|
+
[1, 2, 3]], dtype=float)
|
|
73
|
+
assert np.allclose(cleaned_data, expected_data), "Interpolation in Y direction failed."
|
|
74
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged after interpolation."
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
def test_cleanest_interpolation_surface():
|
|
78
|
+
"""Test cleanest function with surface interpolation."""
|
|
79
|
+
data = np.array([[1, 2, 3],
|
|
80
|
+
[4, 100, 6],
|
|
81
|
+
[7, 8, 9]], dtype=float)
|
|
82
|
+
mask_crfound = np.array([[False, False, False],
|
|
83
|
+
[False, True, False],
|
|
84
|
+
[False, False, False]], dtype=bool)
|
|
85
|
+
|
|
86
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound,
|
|
87
|
+
interp_method='s', npoints=1)
|
|
88
|
+
|
|
89
|
+
expected_data = np.array([[1, 2, 3],
|
|
90
|
+
[4, 5, 6],
|
|
91
|
+
[7, 8, 9]], dtype=float)
|
|
92
|
+
assert np.allclose(cleaned_data, expected_data), "Surface interpolation failed."
|
|
93
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged after interpolation."
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def test_cleanest_interpolation_median():
|
|
97
|
+
"""Test cleanest function with median border pixel interpolation."""
|
|
98
|
+
data = np.array([[1, 2, 3],
|
|
99
|
+
[4, 100, 6],
|
|
100
|
+
[7, 8, 9]], dtype=float)
|
|
101
|
+
mask_crfound = np.array([[False, False, False],
|
|
102
|
+
[False, True, False],
|
|
103
|
+
[False, False, False]], dtype=bool)
|
|
104
|
+
|
|
105
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound,
|
|
106
|
+
interp_method='d', npoints=1)
|
|
107
|
+
|
|
108
|
+
expected_data = np.array([[1, 2, 3],
|
|
109
|
+
[4, 5, 6],
|
|
110
|
+
[7, 8, 9]], dtype=float)
|
|
111
|
+
assert np.allclose(cleaned_data, expected_data), "Median border pixel interpolation failed."
|
|
112
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged after interpolation."
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_cleanest_interpolation_mean():
|
|
116
|
+
"""Test cleanest function with mean border pixel interpolation."""
|
|
117
|
+
data = np.array([[1, 2, 3],
|
|
118
|
+
[4, 100, 6],
|
|
119
|
+
[7, 8, 9]], dtype=float)
|
|
120
|
+
mask_crfound = np.array([[False, False, False],
|
|
121
|
+
[False, True, False],
|
|
122
|
+
[False, False, False]], dtype=bool)
|
|
123
|
+
|
|
124
|
+
cleaned_data, mask_fixed = cleanest(data, mask_crfound,
|
|
125
|
+
interp_method='m', npoints=1)
|
|
126
|
+
|
|
127
|
+
expected_data = np.array([[1, 2, 3],
|
|
128
|
+
[4, 5, 6],
|
|
129
|
+
[7, 8, 9]], dtype=float)
|
|
130
|
+
assert np.allclose(cleaned_data, expected_data), "Mean border pixel interpolation failed."
|
|
131
|
+
assert np.array_equal(mask_fixed, mask_crfound), "Mask should remain unchanged after interpolation."
|
|
@@ -30,8 +30,10 @@ src/teareduce.egg-info/requires.txt
|
|
|
30
30
|
src/teareduce.egg-info/top_level.txt
|
|
31
31
|
src/teareduce/cleanest/__init__.py
|
|
32
32
|
src/teareduce/cleanest/__main__.py
|
|
33
|
+
src/teareduce/cleanest/cleanest.py
|
|
33
34
|
src/teareduce/cleanest/cosmicraycleanerapp.py
|
|
34
35
|
src/teareduce/cleanest/definitions.py
|
|
36
|
+
src/teareduce/cleanest/dilatemask.py
|
|
35
37
|
src/teareduce/cleanest/find_closest_true.py
|
|
36
38
|
src/teareduce/cleanest/imagedisplay.py
|
|
37
39
|
src/teareduce/cleanest/interpolation_a.py
|
|
@@ -43,5 +45,6 @@ src/teareduce/cleanest/reviewcosmicray.py
|
|
|
43
45
|
src/teareduce/cookbook/__init__.py
|
|
44
46
|
src/teareduce/cookbook/get_cookbook_file.py
|
|
45
47
|
src/teareduce/tests/__init__.py
|
|
48
|
+
src/teareduce/tests/test_cleanest.py
|
|
46
49
|
src/teareduce/tests/test_sliceregion.py
|
|
47
50
|
src/teareduce/tests/test_version.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{teareduce-0.5.2/src/teareduce/cleanest → teareduce-0.5.4/src/teareduce/cookbook}/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|