teareduce 0.5.4__py3-none-any.whl → 0.5.7__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/__init__.py +3 -1
- teareduce/cleanest/__main__.py +119 -11
- teareduce/cleanest/centerchildparent.py +46 -0
- teareduce/cleanest/cosmicraycleanerapp.py +517 -232
- teareduce/cleanest/definitions.py +30 -29
- teareduce/cleanest/imagedisplay.py +8 -9
- teareduce/cleanest/{cleanest.py → interpolate.py} +25 -26
- teareduce/cleanest/interpolation_a.py +5 -9
- teareduce/cleanest/interpolationeditor.py +156 -35
- teareduce/cleanest/lacosmicpad.py +57 -0
- teareduce/cleanest/mergemasks.py +58 -0
- teareduce/cleanest/modalprogressbar.py +182 -0
- teareduce/cleanest/parametereditor.py +112 -79
- teareduce/cleanest/reviewcosmicray.py +90 -62
- teareduce/tests/test_cleanest.py +11 -11
- teareduce/version.py +1 -1
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/METADATA +1 -1
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/RECORD +22 -18
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/WHEEL +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/entry_points.txt +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.5.4.dist-info → teareduce-0.5.7.dist-info}/top_level.txt +0 -0
|
@@ -15,42 +15,35 @@
|
|
|
15
15
|
# when reading user input.
|
|
16
16
|
lacosmic_default_dict = {
|
|
17
17
|
# L.A.Cosmic parameters for run 1
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
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": True, "type": bool},
|
|
25
25
|
# L.A.Cosmic parameters for run 2
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
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": True, "type": bool},
|
|
33
33
|
# Dilation of the mask
|
|
34
|
-
|
|
34
|
+
"dilation": {"value": 0, "type": int, "positive": True},
|
|
35
|
+
"borderpadd": {"value": 10, "type": int, "positive": True},
|
|
35
36
|
# Limits for the image section to process (pixels start at 1)
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
37
|
+
"xmin": {"value": 1, "type": int, "positive": True},
|
|
38
|
+
"xmax": {"value": None, "type": int, "positive": True},
|
|
39
|
+
"ymin": {"value": 1, "type": int, "positive": True},
|
|
40
|
+
"ymax": {"value": None, "type": int, "positive": True},
|
|
40
41
|
# Number of runs to execute L.A.Cosmic
|
|
41
|
-
|
|
42
|
+
"nruns": {"value": 1, "type": int, "positive": True},
|
|
42
43
|
}
|
|
43
44
|
|
|
44
45
|
# Default parameters for cleaning methods
|
|
45
|
-
VALID_CLEANING_METHODS = [
|
|
46
|
-
'x interp.',
|
|
47
|
-
'y interp.',
|
|
48
|
-
'surface interp.',
|
|
49
|
-
'median',
|
|
50
|
-
'mean',
|
|
51
|
-
'lacosmic',
|
|
52
|
-
'auxdata'
|
|
53
|
-
]
|
|
46
|
+
VALID_CLEANING_METHODS = ["x interp.", "y interp.", "surface interp.", "median", "mean", "lacosmic", "auxdata"]
|
|
54
47
|
|
|
55
48
|
# Maximum pixel distance to consider when finding closest CR pixel
|
|
56
49
|
MAX_PIXEL_DISTANCE_TO_CR = 15
|
|
@@ -60,3 +53,11 @@ DEFAULT_NPOINTS_INTERP = 2
|
|
|
60
53
|
|
|
61
54
|
# Default degree for interpolation
|
|
62
55
|
DEFAULT_DEGREE_INTERP = 1
|
|
56
|
+
|
|
57
|
+
# Default Tk window size
|
|
58
|
+
DEFAULT_TK_WINDOW_SIZE_X = 800
|
|
59
|
+
DEFAULT_TK_WINDOW_SIZE_Y = 700
|
|
60
|
+
|
|
61
|
+
# Default font settings
|
|
62
|
+
DEFAULT_FONT_FAMILY = "Helvetica"
|
|
63
|
+
DEFAULT_FONT_SIZE = 14
|
|
@@ -60,6 +60,7 @@ class ImageDisplay:
|
|
|
60
60
|
the displayed region can be determined from either the axes limits or a
|
|
61
61
|
predefined region attribute.
|
|
62
62
|
"""
|
|
63
|
+
|
|
63
64
|
def set_vmin(self):
|
|
64
65
|
"""Prompt user to set a new minimum display value (vmin)."""
|
|
65
66
|
old_vmin = self.get_vmin()
|
|
@@ -72,7 +73,7 @@ class ImageDisplay:
|
|
|
72
73
|
return
|
|
73
74
|
self.vmin_button.config(text=f"vmin: {new_vmin:.2f}")
|
|
74
75
|
self.image.set_clim(vmin=new_vmin)
|
|
75
|
-
if hasattr(self,
|
|
76
|
+
if hasattr(self, "image_aux"):
|
|
76
77
|
self.image_aux.set_clim(vmin=new_vmin)
|
|
77
78
|
self.canvas.draw_idle()
|
|
78
79
|
|
|
@@ -88,7 +89,7 @@ class ImageDisplay:
|
|
|
88
89
|
return
|
|
89
90
|
self.vmax_button.config(text=f"vmax: {new_vmax:.2f}")
|
|
90
91
|
self.image.set_clim(vmax=new_vmax)
|
|
91
|
-
if hasattr(self,
|
|
92
|
+
if hasattr(self, "image_aux"):
|
|
92
93
|
self.image_aux.set_clim(vmax=new_vmax)
|
|
93
94
|
self.canvas.draw_idle()
|
|
94
95
|
|
|
@@ -102,7 +103,7 @@ class ImageDisplay:
|
|
|
102
103
|
|
|
103
104
|
def get_displayed_region(self):
|
|
104
105
|
"""Get the currently displayed region of the image."""
|
|
105
|
-
if hasattr(self,
|
|
106
|
+
if hasattr(self, "ax"):
|
|
106
107
|
xmin, xmax = self.ax.get_xlim()
|
|
107
108
|
xmin = int(xmin + 0.5)
|
|
108
109
|
if xmin < 1:
|
|
@@ -118,10 +119,8 @@ class ImageDisplay:
|
|
|
118
119
|
if ymax > self.data.shape[0]:
|
|
119
120
|
ymax = self.data.shape[0]
|
|
120
121
|
print(f"Setting min/max using axis limits: x=({xmin:.2f}, {xmax:.2f}), y=({ymin:.2f}, {ymax:.2f})")
|
|
121
|
-
region = self.region = SliceRegion2D(
|
|
122
|
-
|
|
123
|
-
).python
|
|
124
|
-
elif hasattr(self, 'region'):
|
|
122
|
+
region = self.region = SliceRegion2D(f"[{xmin}:{xmax}, {ymin}:{ymax}]", mode="fits").python
|
|
123
|
+
elif hasattr(self, "region"):
|
|
125
124
|
region = self.region
|
|
126
125
|
else:
|
|
127
126
|
raise AttributeError("No axis or region defined for set_minmax.")
|
|
@@ -136,7 +135,7 @@ class ImageDisplay:
|
|
|
136
135
|
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
137
136
|
self.image.set_clim(vmin=vmin_new)
|
|
138
137
|
self.image.set_clim(vmax=vmax_new)
|
|
139
|
-
if hasattr(self,
|
|
138
|
+
if hasattr(self, "image_aux"):
|
|
140
139
|
self.image_aux.set_clim(vmin=vmin_new)
|
|
141
140
|
self.image_aux.set_clim(vmax=vmax_new)
|
|
142
141
|
self.canvas.draw_idle()
|
|
@@ -149,7 +148,7 @@ class ImageDisplay:
|
|
|
149
148
|
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
150
149
|
self.image.set_clim(vmin=vmin_new)
|
|
151
150
|
self.image.set_clim(vmax=vmax_new)
|
|
152
|
-
if hasattr(self,
|
|
151
|
+
if hasattr(self, "image_aux"):
|
|
153
152
|
self.image_aux.set_clim(vmin=vmin_new)
|
|
154
153
|
self.image_aux.set_clim(vmax=vmax_new)
|
|
155
154
|
self.canvas.draw_idle()
|
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
|
|
12
12
|
from scipy import ndimage
|
|
13
13
|
import numpy as np
|
|
14
|
+
from tqdm import tqdm
|
|
14
15
|
|
|
15
16
|
from .dilatemask import dilatemask
|
|
16
17
|
from .interpolation_x import interpolation_x
|
|
@@ -18,9 +19,7 @@ from .interpolation_y import interpolation_y
|
|
|
18
19
|
from .interpolation_a import interpolation_a
|
|
19
20
|
|
|
20
21
|
|
|
21
|
-
def
|
|
22
|
-
interp_method=None, npoints=None, degree=None,
|
|
23
|
-
debug=False):
|
|
22
|
+
def interpolate(data, mask_crfound, dilation=0, interp_method=None, npoints=None, degree=None, debug=False):
|
|
24
23
|
"""Interpolate pixels identified in a mask.
|
|
25
24
|
|
|
26
25
|
The original data and mask are not modified. A copy of both
|
|
@@ -31,8 +30,8 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
31
30
|
data : 2D numpy.ndarray
|
|
32
31
|
The image data array to be processed.
|
|
33
32
|
mask_crfound : 2D numpy.ndarray of bool
|
|
34
|
-
A boolean mask array indicating which pixels are
|
|
35
|
-
|
|
33
|
+
A boolean mask array indicating which pixels are flagged
|
|
34
|
+
and need to be interpolated (True = pixel to be fixed).
|
|
36
35
|
dilation : int, optional
|
|
37
36
|
The number of pixels to dilate the masked pixels before
|
|
38
37
|
interpolation.
|
|
@@ -50,7 +49,7 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
50
49
|
The degree of the polynomial to fit. This parameter is
|
|
51
50
|
relevant for 'x' and 'y' methods.
|
|
52
51
|
debug : bool, optional
|
|
53
|
-
If True, print debug information.
|
|
52
|
+
If True, print debug information and enable tqdm progress bar.
|
|
54
53
|
|
|
55
54
|
Returns
|
|
56
55
|
-------
|
|
@@ -68,11 +67,11 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
68
67
|
"""
|
|
69
68
|
if interp_method is None:
|
|
70
69
|
raise ValueError("interp_method must be specified.")
|
|
71
|
-
if interp_method not in [
|
|
70
|
+
if interp_method not in ["x", "y", "s", "d", "m"]:
|
|
72
71
|
raise ValueError(f"Unknown interp_method: {interp_method}")
|
|
73
72
|
if npoints is None:
|
|
74
73
|
raise ValueError("npoints must be specified.")
|
|
75
|
-
if degree is None and interp_method in [
|
|
74
|
+
if degree is None and interp_method in ["x", "y"]:
|
|
76
75
|
raise ValueError("degree must be specified for the chosen interp_method.")
|
|
77
76
|
|
|
78
77
|
# Apply dilation to the cosmic ray mask if needed
|
|
@@ -85,22 +84,21 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
85
84
|
mask_fixed = np.zeros_like(mask_crfound, dtype=bool)
|
|
86
85
|
|
|
87
86
|
# Determine number of CR features
|
|
88
|
-
structure = [[1, 1, 1],
|
|
89
|
-
[1, 1, 1],
|
|
90
|
-
[1, 1, 1]]
|
|
87
|
+
structure = [[1, 1, 1], [1, 1, 1], [1, 1, 1]]
|
|
91
88
|
cr_labels, num_features = ndimage.label(updated_mask_crfound, structure=structure)
|
|
92
89
|
if debug:
|
|
93
|
-
|
|
94
|
-
print(f"Number of cosmic
|
|
90
|
+
sdum = str(np.sum(updated_mask_crfound))
|
|
91
|
+
print(f"Number of cosmic ray pixels to be cleaned: {sdum}")
|
|
92
|
+
print(f"Number of cosmic rays (grouped pixels)...: {num_features:>{len(sdum)}}")
|
|
95
93
|
|
|
96
94
|
# Fix cosmic rays using the specified interpolation method
|
|
97
95
|
cleaned_data = data.copy()
|
|
98
96
|
num_cr_cleaned = 0
|
|
99
|
-
for cr_index in range(1, num_features + 1):
|
|
100
|
-
if interp_method in [
|
|
97
|
+
for cr_index in tqdm(range(1, num_features + 1), disable=not debug):
|
|
98
|
+
if interp_method in ["x", "y"]:
|
|
101
99
|
if 2 * npoints <= degree:
|
|
102
100
|
raise ValueError("2*npoints must be greater than degree for polynomial interpolation.")
|
|
103
|
-
if interp_method ==
|
|
101
|
+
if interp_method == "x":
|
|
104
102
|
interp_func = interpolation_x
|
|
105
103
|
else:
|
|
106
104
|
interp_func = interpolation_y
|
|
@@ -110,23 +108,24 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
110
108
|
cr_labels=cr_labels,
|
|
111
109
|
cr_index=cr_index,
|
|
112
110
|
npoints=npoints,
|
|
113
|
-
degree=degree
|
|
111
|
+
degree=degree,
|
|
112
|
+
)
|
|
114
113
|
if interpolation_performed:
|
|
115
114
|
num_cr_cleaned += 1
|
|
116
|
-
elif interp_method in [
|
|
117
|
-
if interp_method ==
|
|
118
|
-
method =
|
|
119
|
-
elif interp_method ==
|
|
120
|
-
method =
|
|
121
|
-
elif interp_method ==
|
|
122
|
-
method =
|
|
115
|
+
elif interp_method in ["s", "d", "m"]:
|
|
116
|
+
if interp_method == "s":
|
|
117
|
+
method = "surface"
|
|
118
|
+
elif interp_method == "d":
|
|
119
|
+
method = "median"
|
|
120
|
+
elif interp_method == "m":
|
|
121
|
+
method = "mean"
|
|
123
122
|
interpolation_performed, _, _ = interpolation_a(
|
|
124
123
|
data=cleaned_data,
|
|
125
124
|
mask_fixed=mask_fixed,
|
|
126
125
|
cr_labels=cr_labels,
|
|
127
126
|
cr_index=cr_index,
|
|
128
127
|
npoints=npoints,
|
|
129
|
-
method=method
|
|
128
|
+
method=method,
|
|
130
129
|
)
|
|
131
130
|
if interpolation_performed:
|
|
132
131
|
num_cr_cleaned += 1
|
|
@@ -134,6 +133,6 @@ def cleanest(data, mask_crfound, dilation=0,
|
|
|
134
133
|
raise ValueError(f"Unknown interpolation method: {interp_method}")
|
|
135
134
|
|
|
136
135
|
if debug:
|
|
137
|
-
print(f"Number of cosmic rays cleaned............: {num_cr_cleaned}")
|
|
136
|
+
print(f"Number of cosmic rays cleaned............: {num_cr_cleaned:>{len(sdum)}}")
|
|
138
137
|
|
|
139
138
|
return cleaned_data, mask_fixed
|
|
@@ -53,14 +53,10 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
53
53
|
by cosmic rays are interpolated.
|
|
54
54
|
"""
|
|
55
55
|
# Mask of CR pixels
|
|
56
|
-
mask =
|
|
56
|
+
mask = cr_labels == cr_index
|
|
57
57
|
# Dilate the mask to find border pixels
|
|
58
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
|
-
)
|
|
59
|
+
dilated_mask = dilatemask(mask=mask, iterations=npoints, connectivity=1)
|
|
64
60
|
# Border pixels are those in the dilated mask but not in the original mask
|
|
65
61
|
border_mask = dilated_mask & (~mask)
|
|
66
62
|
# Get coordinates of border pixels
|
|
@@ -68,7 +64,7 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
68
64
|
zfit_all = data[yfit_all, xfit_all].tolist()
|
|
69
65
|
# Perform interpolation
|
|
70
66
|
interpolation_performed = False
|
|
71
|
-
if method ==
|
|
67
|
+
if method == "surface":
|
|
72
68
|
if len(xfit_all) > 3:
|
|
73
69
|
# Construct the design matrix for a 2D polynomial fit to a plane,
|
|
74
70
|
# where each row corresponds to a point (x, y, z) and the model
|
|
@@ -84,10 +80,10 @@ def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
|
84
80
|
interpolation_performed = True
|
|
85
81
|
else:
|
|
86
82
|
print("Not enough points to fit a plane")
|
|
87
|
-
elif method in [
|
|
83
|
+
elif method in ["median", "mean"]:
|
|
88
84
|
# Compute median of all surrounding points
|
|
89
85
|
if len(zfit_all) > 0:
|
|
90
|
-
if method ==
|
|
86
|
+
if method == "median":
|
|
91
87
|
zval = np.median(zfit_all)
|
|
92
88
|
else:
|
|
93
89
|
zval = np.mean(zfit_all)
|
|
@@ -11,13 +11,16 @@
|
|
|
11
11
|
|
|
12
12
|
import tkinter as tk
|
|
13
13
|
from tkinter import messagebox
|
|
14
|
+
from tkinter import ttk
|
|
14
15
|
|
|
16
|
+
from .centerchildparent import center_on_parent
|
|
15
17
|
from .definitions import VALID_CLEANING_METHODS
|
|
16
18
|
|
|
17
19
|
|
|
18
20
|
class InterpolationEditor:
|
|
19
21
|
"""Dialog to select interpolation cleaning parameters."""
|
|
20
|
-
|
|
22
|
+
|
|
23
|
+
def __init__(self, root, last_dilation, last_npoints, last_degree, auxdata, xmin, xmax, ymin, ymax, imgshape):
|
|
21
24
|
"""Initialize the interpolation editor dialog.
|
|
22
25
|
|
|
23
26
|
Parameters
|
|
@@ -32,6 +35,16 @@ class InterpolationEditor:
|
|
|
32
35
|
The last used degree for interpolation.
|
|
33
36
|
auxdata : array-like or None
|
|
34
37
|
Auxiliary data for cleaning, if available.
|
|
38
|
+
xmin : float
|
|
39
|
+
Minimum x value of the data. From 1 to NAXIS1.
|
|
40
|
+
xmax : float
|
|
41
|
+
Maximum x value of the data. From 1 to NAXIS1.
|
|
42
|
+
ymin : float
|
|
43
|
+
Minimum y value of the data. From 1 to NAXIS2.
|
|
44
|
+
ymax : float
|
|
45
|
+
Maximum y value of the data. From 1 to NAXIS2.
|
|
46
|
+
imgshape : tuple
|
|
47
|
+
Shape of the image data (height, width).
|
|
35
48
|
|
|
36
49
|
Methods
|
|
37
50
|
-------
|
|
@@ -62,6 +75,16 @@ class InterpolationEditor:
|
|
|
62
75
|
The number of points for interpolation.
|
|
63
76
|
degree : int
|
|
64
77
|
The degree for interpolation.
|
|
78
|
+
xmin : float
|
|
79
|
+
Minimum x value of the data. From 1 to NAXIS1.
|
|
80
|
+
xmax : float
|
|
81
|
+
Maximum x value of the data. From 1 to NAXIS1.
|
|
82
|
+
ymin : float
|
|
83
|
+
Minimum y value of the data. From 1 to NAXIS2.
|
|
84
|
+
ymax : float
|
|
85
|
+
Maximum y value of the data. From 1 to NAXIS2.
|
|
86
|
+
imgshape : tuple
|
|
87
|
+
Shape of the image data (height, width).
|
|
65
88
|
"""
|
|
66
89
|
self.root = root
|
|
67
90
|
self.root.title("Cleaning Parameters")
|
|
@@ -74,15 +97,23 @@ class InterpolationEditor:
|
|
|
74
97
|
"median": "a-median",
|
|
75
98
|
"mean": "a-mean",
|
|
76
99
|
"lacosmic": "lacosmic",
|
|
77
|
-
"auxdata": "auxdata"
|
|
100
|
+
"auxdata": "auxdata",
|
|
78
101
|
}
|
|
79
102
|
self.check_interp_methods()
|
|
80
103
|
# Initialize parameters
|
|
81
104
|
self.cleaning_method = None
|
|
82
105
|
self.npoints = last_npoints
|
|
83
106
|
self.degree = last_degree
|
|
107
|
+
self.xmin = xmin
|
|
108
|
+
self.xmax = xmax
|
|
109
|
+
self.ymin = ymin
|
|
110
|
+
self.ymax = ymax
|
|
111
|
+
self.imgshape = imgshape
|
|
112
|
+
# Dictionary to hold entry widgets for region parameters
|
|
113
|
+
self.entries = {}
|
|
84
114
|
# Create the form
|
|
85
115
|
self.create_widgets()
|
|
116
|
+
center_on_parent(child=self.root, parent=self.root.master)
|
|
86
117
|
|
|
87
118
|
def create_widgets(self):
|
|
88
119
|
"""Create the widgets for the dialog."""
|
|
@@ -90,9 +121,15 @@ class InterpolationEditor:
|
|
|
90
121
|
main_frame = tk.Frame(self.root, padx=10, pady=10)
|
|
91
122
|
main_frame.pack()
|
|
92
123
|
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
124
|
+
row = 0
|
|
125
|
+
|
|
126
|
+
# Subtitle for cleaning method selection
|
|
127
|
+
default_font = tk.font.nametofont("TkDefaultFont")
|
|
128
|
+
bold_font = default_font.copy()
|
|
129
|
+
bold_font.configure(weight="bold", size=default_font.cget("size") + 2)
|
|
130
|
+
subtitle_label = tk.Label(main_frame, text="Select Cleaning Method", font=bold_font)
|
|
131
|
+
subtitle_label.grid(row=row, column=0, columnspan=3, pady=(0, 15))
|
|
132
|
+
row += 1
|
|
96
133
|
|
|
97
134
|
# Create labels and entry fields for each cleaning method
|
|
98
135
|
row = 1
|
|
@@ -111,35 +148,80 @@ class InterpolationEditor:
|
|
|
111
148
|
text=interp_method,
|
|
112
149
|
variable=self.cleaning_method_var,
|
|
113
150
|
value=interp_method,
|
|
114
|
-
command=self.action_on_method_change
|
|
115
|
-
).grid(row=row, column=
|
|
151
|
+
command=self.action_on_method_change,
|
|
152
|
+
).grid(row=row, column=1, sticky="w")
|
|
116
153
|
row += 1
|
|
117
154
|
|
|
155
|
+
# Separator
|
|
156
|
+
separator1 = ttk.Separator(main_frame, orient="horizontal")
|
|
157
|
+
separator1.grid(row=row, column=0, columnspan=3, sticky="ew", pady=(10, 10))
|
|
158
|
+
row += 1
|
|
159
|
+
|
|
160
|
+
# Subtitle for additional parameters
|
|
161
|
+
subtitle_label = tk.Label(main_frame, text="Additional Parameters", font=bold_font)
|
|
162
|
+
subtitle_label.grid(row=row, column=0, columnspan=3, pady=(0, 15))
|
|
163
|
+
row += 1
|
|
164
|
+
|
|
118
165
|
# Create labels and entry fields for each additional parameter
|
|
119
|
-
label = tk.Label(main_frame, text=
|
|
120
|
-
label.grid(row=row, column=0, sticky=
|
|
166
|
+
label = tk.Label(main_frame, text="Npoints:")
|
|
167
|
+
label.grid(row=row, column=0, sticky="e", padx=(0, 10))
|
|
121
168
|
self.entry_npoints = tk.Entry(main_frame, width=10)
|
|
122
169
|
self.entry_npoints.insert(0, self.npoints)
|
|
123
|
-
self.entry_npoints.grid(row=row, column=1, sticky=
|
|
170
|
+
self.entry_npoints.grid(row=row, column=1, sticky="w")
|
|
124
171
|
row += 1
|
|
125
|
-
label = tk.Label(main_frame, text=
|
|
126
|
-
label.grid(row=row, column=0, sticky=
|
|
172
|
+
label = tk.Label(main_frame, text="Degree:")
|
|
173
|
+
label.grid(row=row, column=0, sticky="e", padx=(0, 10))
|
|
127
174
|
self.entry_degree = tk.Entry(main_frame, width=10)
|
|
128
175
|
self.entry_degree.insert(0, self.degree)
|
|
129
|
-
self.entry_degree.grid(row=row, column=1, sticky=
|
|
176
|
+
self.entry_degree.grid(row=row, column=1, sticky="w")
|
|
177
|
+
row += 1
|
|
178
|
+
|
|
179
|
+
# Separator
|
|
180
|
+
separator2 = ttk.Separator(main_frame, orient="horizontal")
|
|
181
|
+
separator2.grid(row=row, column=0, columnspan=3, sticky="ew", pady=(10, 10))
|
|
182
|
+
row += 1
|
|
183
|
+
|
|
184
|
+
# Subtitle for region to be examined
|
|
185
|
+
subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=bold_font)
|
|
186
|
+
subtitle_label.grid(row=row, column=0, columnspan=3, pady=(0, 15))
|
|
187
|
+
row += 1
|
|
188
|
+
|
|
189
|
+
# Region to be examined label and entries
|
|
190
|
+
for key in ["xmin", "xmax", "ymin", "ymax"]:
|
|
191
|
+
# Parameter name label
|
|
192
|
+
label = tk.Label(main_frame, text=f"{key}:", anchor="e", width=15)
|
|
193
|
+
label.grid(row=row, column=0, sticky="w", pady=5)
|
|
194
|
+
# Entry field
|
|
195
|
+
entry = tk.Entry(main_frame, width=10)
|
|
196
|
+
entry.insert(0, str(self.__dict__[key]))
|
|
197
|
+
entry.grid(row=row, column=1, padx=10, pady=5)
|
|
198
|
+
self.entries[key] = entry # dictionary to hold entry widgets
|
|
199
|
+
# Type label
|
|
200
|
+
dumtext = "(int)"
|
|
201
|
+
if key in ["xmin", "xmax"]:
|
|
202
|
+
dumtext += f" --> [1, {self.imgshape[1]}]"
|
|
203
|
+
else:
|
|
204
|
+
dumtext += f" --> [1, {self.imgshape[0]}]"
|
|
205
|
+
type_label = tk.Label(main_frame, text=dumtext, fg="gray", anchor="w", width=15)
|
|
206
|
+
type_label.grid(row=row, column=2, sticky="w", pady=5)
|
|
207
|
+
row += 1
|
|
208
|
+
|
|
209
|
+
# Separator
|
|
210
|
+
separator3 = ttk.Separator(main_frame, orient="horizontal")
|
|
211
|
+
separator3.grid(row=row, column=0, columnspan=3, sticky="ew", pady=(10, 10))
|
|
130
212
|
row += 1
|
|
131
213
|
|
|
132
214
|
# Button frame
|
|
133
215
|
self.button_frame = tk.Frame(main_frame)
|
|
134
|
-
self.button_frame.grid(row=row, column=0, columnspan=
|
|
216
|
+
self.button_frame.grid(row=row, column=0, columnspan=3, pady=(15, 0))
|
|
135
217
|
|
|
136
218
|
# OK button
|
|
137
219
|
self.ok_button = tk.Button(self.button_frame, text="OK", width=5, command=self.on_ok)
|
|
138
|
-
self.ok_button.pack(side=
|
|
220
|
+
self.ok_button.pack(side="left", padx=5)
|
|
139
221
|
|
|
140
222
|
# Cancel button
|
|
141
223
|
self.cancel_button = tk.Button(self.button_frame, text="Cancel", width=5, command=self.on_cancel)
|
|
142
|
-
self.cancel_button.pack(side=
|
|
224
|
+
self.cancel_button.pack(side="left", padx=5)
|
|
143
225
|
|
|
144
226
|
# Initial action depending on the default method
|
|
145
227
|
self.action_on_method_change()
|
|
@@ -165,10 +247,49 @@ class InterpolationEditor:
|
|
|
165
247
|
messagebox.showerror("Input Error", "Degree must be non-negative.")
|
|
166
248
|
return
|
|
167
249
|
|
|
168
|
-
if self.cleaning_method in [
|
|
250
|
+
if self.cleaning_method in ["x", "y"] and 2 * self.npoints <= self.degree:
|
|
169
251
|
messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x and y interpolation.")
|
|
170
252
|
return
|
|
171
253
|
|
|
254
|
+
# Retrieve and validate region parameters
|
|
255
|
+
try:
|
|
256
|
+
xmin = int(self.entries["xmin"].get())
|
|
257
|
+
except ValueError:
|
|
258
|
+
messagebox.showerror("Input Error", "xmin must be an integer.")
|
|
259
|
+
return
|
|
260
|
+
try:
|
|
261
|
+
xmax = int(self.entries["xmax"].get())
|
|
262
|
+
except ValueError:
|
|
263
|
+
messagebox.showerror("Input Error", "xmax must be an integer.")
|
|
264
|
+
return
|
|
265
|
+
if xmin >= xmax:
|
|
266
|
+
messagebox.showerror("Input Error", "xmin must be less than xmax.")
|
|
267
|
+
return
|
|
268
|
+
try:
|
|
269
|
+
ymin = int(self.entries["ymin"].get())
|
|
270
|
+
except ValueError:
|
|
271
|
+
messagebox.showerror("Input Error", "ymin must be an integer.")
|
|
272
|
+
return
|
|
273
|
+
try:
|
|
274
|
+
ymax = int(self.entries["ymax"].get())
|
|
275
|
+
except ValueError:
|
|
276
|
+
messagebox.showerror("Input Error", "ymax must be an integer.")
|
|
277
|
+
return
|
|
278
|
+
if ymin >= ymax:
|
|
279
|
+
messagebox.showerror("Input Error", "ymin must be less than ymax.")
|
|
280
|
+
return
|
|
281
|
+
for key, entry in self.entries.items():
|
|
282
|
+
value = int(entry.get())
|
|
283
|
+
if key in ["xmin", "xmax"]:
|
|
284
|
+
if not (1 <= value <= self.imgshape[1]):
|
|
285
|
+
messagebox.showerror("Input Error", f"{key} must be in the range [1, {self.imgshape[1]}].")
|
|
286
|
+
return
|
|
287
|
+
else:
|
|
288
|
+
if not (1 <= value <= self.imgshape[0]):
|
|
289
|
+
messagebox.showerror("Input Error", f"{key} must be in the range [1, {self.imgshape[0]}].")
|
|
290
|
+
return
|
|
291
|
+
self.__dict__[key] = value
|
|
292
|
+
|
|
172
293
|
self.root.destroy()
|
|
173
294
|
|
|
174
295
|
def on_cancel(self):
|
|
@@ -182,24 +303,24 @@ class InterpolationEditor:
|
|
|
182
303
|
"""Handle changes in the selected cleaning method."""
|
|
183
304
|
selected_method = self.cleaning_method_var.get()
|
|
184
305
|
print(f"Selected cleaning method: {selected_method}")
|
|
185
|
-
if selected_method in [
|
|
186
|
-
self.entry_npoints.config(state=
|
|
187
|
-
self.entry_degree.config(state=
|
|
188
|
-
elif selected_method ==
|
|
189
|
-
self.entry_npoints.config(state=
|
|
190
|
-
self.entry_degree.config(state=
|
|
191
|
-
elif selected_method ==
|
|
192
|
-
self.entry_npoints.config(state=
|
|
193
|
-
self.entry_degree.config(state=
|
|
194
|
-
elif selected_method ==
|
|
195
|
-
self.entry_npoints.config(state=
|
|
196
|
-
self.entry_degree.config(state=
|
|
197
|
-
elif selected_method ==
|
|
198
|
-
self.entry_npoints.config(state=
|
|
199
|
-
self.entry_degree.config(state=
|
|
200
|
-
elif selected_method ==
|
|
201
|
-
self.entry_npoints.config(state=
|
|
202
|
-
self.entry_degree.config(state=
|
|
306
|
+
if selected_method in ["x interp.", "y interp."]:
|
|
307
|
+
self.entry_npoints.config(state="normal")
|
|
308
|
+
self.entry_degree.config(state="normal")
|
|
309
|
+
elif selected_method == "surface interp.":
|
|
310
|
+
self.entry_npoints.config(state="normal")
|
|
311
|
+
self.entry_degree.config(state="disabled")
|
|
312
|
+
elif selected_method == "median":
|
|
313
|
+
self.entry_npoints.config(state="normal")
|
|
314
|
+
self.entry_degree.config(state="disabled")
|
|
315
|
+
elif selected_method == "mean":
|
|
316
|
+
self.entry_npoints.config(state="normal")
|
|
317
|
+
self.entry_degree.config(state="disabled")
|
|
318
|
+
elif selected_method == "lacosmic":
|
|
319
|
+
self.entry_npoints.config(state="disabled")
|
|
320
|
+
self.entry_degree.config(state="disabled")
|
|
321
|
+
elif selected_method == "auxdata":
|
|
322
|
+
self.entry_npoints.config(state="disabled")
|
|
323
|
+
self.entry_degree.config(state="disabled")
|
|
203
324
|
|
|
204
325
|
def check_interp_methods(self):
|
|
205
326
|
"""Check that all interpolation methods are valid."""
|
|
@@ -0,0 +1,57 @@
|
|
|
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
|
+
"""Execute LACosmic algorithm on a padded image."""
|
|
11
|
+
|
|
12
|
+
from ccdproc import cosmicray_lacosmic
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def lacosmicpad(pad_width, **kwargs):
|
|
17
|
+
"""Execute LACosmic algorithm on a padded array.
|
|
18
|
+
|
|
19
|
+
This function pads the input image array before applying the LACosmic
|
|
20
|
+
cosmic ray cleaning algorithm. After processing, the padding is removed
|
|
21
|
+
to return an array of the original size.
|
|
22
|
+
|
|
23
|
+
The padding helps to mitigate edge effects that can occur during the
|
|
24
|
+
cosmic ray detection and cleaning process.
|
|
25
|
+
|
|
26
|
+
Apart from the `pad_width` parameter, all other keyword arguments
|
|
27
|
+
are passed directly to the `cosmicray_lacosmic` function from the
|
|
28
|
+
`ccdproc` package.
|
|
29
|
+
|
|
30
|
+
Parameters
|
|
31
|
+
----------
|
|
32
|
+
pad_width : int
|
|
33
|
+
Width of the padding to be applied to the image before executing
|
|
34
|
+
the LACosmic algorithm.
|
|
35
|
+
**kwargs : dict
|
|
36
|
+
Keyword arguments to be passed to the `cosmicray_lacosmic` function.
|
|
37
|
+
|
|
38
|
+
Returns
|
|
39
|
+
-------
|
|
40
|
+
clean_array : 2D numpy.ndarray
|
|
41
|
+
The cleaned image array after applying the LACosmic algorithm with padding.
|
|
42
|
+
mask_array : 2D numpy.ndarray of bool
|
|
43
|
+
The mask array indicating detected cosmic rays.
|
|
44
|
+
"""
|
|
45
|
+
if "ccd" not in kwargs:
|
|
46
|
+
raise ValueError("The 'ccd' keyword argument must be provided.")
|
|
47
|
+
array = kwargs.pop("ccd")
|
|
48
|
+
if not isinstance(array, np.ndarray):
|
|
49
|
+
raise TypeError("The 'ccd' keyword argument must be a numpy ndarray.")
|
|
50
|
+
# Pad the array
|
|
51
|
+
padded_array = np.pad(array, pad_width, mode="reflect")
|
|
52
|
+
# Apply LACosmic algorithm to the padded array
|
|
53
|
+
cleaned_padded_array, mask_padded_array = cosmicray_lacosmic(ccd=padded_array, **kwargs)
|
|
54
|
+
# Remove padding
|
|
55
|
+
cleaned_array = cleaned_padded_array[pad_width:-pad_width, pad_width:-pad_width]
|
|
56
|
+
mask_array = mask_padded_array[pad_width:-pad_width, pad_width:-pad_width]
|
|
57
|
+
return cleaned_array, mask_array
|