teareduce 0.4.9__py3-none-any.whl → 0.5.1__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- teareduce/cleanest/__main__.py +18 -6
- teareduce/cleanest/cosmicraycleanerapp.py +586 -85
- teareduce/cleanest/definitions.py +51 -0
- teareduce/cleanest/find_closest_true.py +44 -0
- teareduce/cleanest/imagedisplay.py +155 -0
- teareduce/cleanest/interpolation_a.py +83 -0
- teareduce/cleanest/interpolation_x.py +80 -0
- teareduce/cleanest/interpolation_y.py +81 -0
- teareduce/cleanest/interpolationeditor.py +207 -0
- teareduce/cleanest/parametereditor.py +251 -0
- teareduce/cleanest/reviewcosmicray.py +351 -267
- teareduce/cookbook/get_cookbook_file.py +5 -0
- teareduce/version.py +1 -1
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/METADATA +4 -1
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/RECORD +19 -11
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/WHEEL +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/entry_points.txt +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.4.9.dist-info → teareduce-0.5.1.dist-info}/top_level.txt +0 -0
|
@@ -0,0 +1,51 @@
|
|
|
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
|
+
"""Definitions for the cleanest module."""
|
|
11
|
+
|
|
12
|
+
# Default parameters for L.A.Cosmic algorithm
|
|
13
|
+
# Note that 'type' is set to the expected data type for each parameter
|
|
14
|
+
# using the intrinsic Python types, so that they can be easily cast
|
|
15
|
+
# when reading user input.
|
|
16
|
+
lacosmic_default_dict = {
|
|
17
|
+
# L.A.Cosmic parameters
|
|
18
|
+
'gain': {'value': 1.0, 'type': float, 'positive': True},
|
|
19
|
+
'readnoise': {'value': 6.5, 'type': float, 'positive': True},
|
|
20
|
+
'sigclip': {'value': 4.5, 'type': float, 'positive': True},
|
|
21
|
+
'sigfrac': {'value': 0.3, 'type': float, 'positive': True},
|
|
22
|
+
'objlim': {'value': 5.0, 'type': float, 'positive': True},
|
|
23
|
+
'niter': {'value': 4, 'type': int, 'positive': True},
|
|
24
|
+
'verbose': {'value': False, 'type': bool},
|
|
25
|
+
# Dilation of the mask
|
|
26
|
+
'dilation': {'value': 0, 'type': int, 'positive': True},
|
|
27
|
+
# Limits for the image section to process (pixels start at 1)
|
|
28
|
+
'xmin': {'value': 1, 'type': int, 'positive': True},
|
|
29
|
+
'xmax': {'value': None, 'type': int, 'positive': True},
|
|
30
|
+
'ymin': {'value': 1, 'type': int, 'positive': True},
|
|
31
|
+
'ymax': {'value': None, 'type': int, 'positive': True}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
# Default parameters for cleaning methods
|
|
35
|
+
VALID_CLEANING_METHODS = [
|
|
36
|
+
'x interp.',
|
|
37
|
+
'y interp.',
|
|
38
|
+
'surface interp.',
|
|
39
|
+
'median',
|
|
40
|
+
'lacosmic',
|
|
41
|
+
'auxdata'
|
|
42
|
+
]
|
|
43
|
+
|
|
44
|
+
# Maximum pixel distance to consider when finding closest CR pixel
|
|
45
|
+
MAX_PIXEL_DISTANCE_TO_CR = 15
|
|
46
|
+
|
|
47
|
+
# Default number of points for interpolation
|
|
48
|
+
DEFAULT_NPOINTS_INTERP = 2
|
|
49
|
+
|
|
50
|
+
# Default degree for interpolation
|
|
51
|
+
DEFAULT_DEGREE_INTERP = 1
|
|
@@ -0,0 +1,44 @@
|
|
|
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
|
+
"""Find the closest true (CR) pixel to a given (x, y) position."""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def find_closest_true(mask, x, y):
|
|
16
|
+
"""Find the closest True pixel in a boolean mask to the given (x, y) position.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
mask : 2D numpy.ndarray of bool
|
|
21
|
+
Boolean mask where True indicates the presence of a cosmic ray pixel.
|
|
22
|
+
x : int
|
|
23
|
+
X-coordinate (column index) of the reference position (0-based).
|
|
24
|
+
y : int
|
|
25
|
+
Y-coordinate (row index) of the reference position (0-based).
|
|
26
|
+
|
|
27
|
+
Returns
|
|
28
|
+
-------
|
|
29
|
+
(closest_x, closest_y) : tuple of int
|
|
30
|
+
Coordinates (0-based) of the closest True pixel in the mask.
|
|
31
|
+
Returns (None, None) if no True pixels are found.
|
|
32
|
+
min_distance : float
|
|
33
|
+
Euclidean distance to the closest True pixel.
|
|
34
|
+
"""
|
|
35
|
+
true_indices = np.argwhere(mask)
|
|
36
|
+
if true_indices.size == 0:
|
|
37
|
+
return None, None
|
|
38
|
+
|
|
39
|
+
distances = np.sqrt((true_indices[:, 1] - x) ** 2 + (true_indices[:, 0] - y) ** 2)
|
|
40
|
+
min_index = np.argmin(distances)
|
|
41
|
+
closest_y, closest_x = true_indices[min_index]
|
|
42
|
+
min_distance = distances[min_index]
|
|
43
|
+
|
|
44
|
+
return (closest_x, closest_y), min_distance
|
|
@@ -0,0 +1,155 @@
|
|
|
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
|
+
"""Base class for image display with min/max and zscale controls."""
|
|
11
|
+
|
|
12
|
+
from tkinter import simpledialog
|
|
13
|
+
import numpy as np
|
|
14
|
+
|
|
15
|
+
from ..sliceregion import SliceRegion2D
|
|
16
|
+
from ..zscale import zscale
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
# The functionality defined here is used in multiple classes
|
|
20
|
+
class ImageDisplay:
|
|
21
|
+
"""Class to handle image display with min/max and zscale controls.
|
|
22
|
+
|
|
23
|
+
Methods
|
|
24
|
+
-------
|
|
25
|
+
set_vmin()
|
|
26
|
+
Prompt user to set a new minimum display value (vmin).
|
|
27
|
+
set_vmax()
|
|
28
|
+
Prompt user to set a new maximum display value (vmax).
|
|
29
|
+
get_vmin()
|
|
30
|
+
Get the current minimum display value (vmin).
|
|
31
|
+
get_vmax()
|
|
32
|
+
Get the current maximum display value (vmax).
|
|
33
|
+
get_displayed_region()
|
|
34
|
+
Get the currently displayed region of the image.
|
|
35
|
+
set_minmax()
|
|
36
|
+
Set vmin and vmax based on the currently displayed region.
|
|
37
|
+
set_zscale()
|
|
38
|
+
Set vmin and vmax using zscale on the currently displayed region.
|
|
39
|
+
|
|
40
|
+
Attributes
|
|
41
|
+
----------
|
|
42
|
+
vmin_button : tkinter.Button
|
|
43
|
+
Button to display and set the minimum display value (vmin).
|
|
44
|
+
vmax_button : tkinter.Button
|
|
45
|
+
Button to display and set the maximum display value (vmax).
|
|
46
|
+
image : matplotlib.image.AxesImage
|
|
47
|
+
The main image being displayed.
|
|
48
|
+
image_aux : matplotlib.image.AxesImage, optional
|
|
49
|
+
An auxiliary image being displayed (if any).
|
|
50
|
+
canvas : matplotlib.backends.backend_tkagg.FigureCanvasTkAgg
|
|
51
|
+
The canvas on which the image is drawn.
|
|
52
|
+
|
|
53
|
+
Notes
|
|
54
|
+
-----
|
|
55
|
+
This class is intented to be used as a parent class for different
|
|
56
|
+
classes that display images and need functionality to adjust the
|
|
57
|
+
display limits (vmin and vmax) interactively.
|
|
58
|
+
|
|
59
|
+
This class assumes that the image data is stored in `self.data` and that
|
|
60
|
+
the displayed region can be determined from either the axes limits or a
|
|
61
|
+
predefined region attribute.
|
|
62
|
+
"""
|
|
63
|
+
def set_vmin(self):
|
|
64
|
+
"""Prompt user to set a new minimum display value (vmin)."""
|
|
65
|
+
old_vmin = self.get_vmin()
|
|
66
|
+
old_vmax = self.get_vmax()
|
|
67
|
+
new_vmin = simpledialog.askfloat("Set vmin", "Enter new vmin:", initialvalue=old_vmin)
|
|
68
|
+
if new_vmin is None:
|
|
69
|
+
return
|
|
70
|
+
if new_vmin >= old_vmax:
|
|
71
|
+
print("Error: vmin must be less than vmax.")
|
|
72
|
+
return
|
|
73
|
+
self.vmin_button.config(text=f"vmin: {new_vmin:.2f}")
|
|
74
|
+
self.image.set_clim(vmin=new_vmin)
|
|
75
|
+
if hasattr(self, 'image_aux'):
|
|
76
|
+
self.image_aux.set_clim(vmin=new_vmin)
|
|
77
|
+
self.canvas.draw_idle()
|
|
78
|
+
|
|
79
|
+
def set_vmax(self):
|
|
80
|
+
"""Prompt user to set a new maximum display value (vmax)."""
|
|
81
|
+
old_vmin = self.get_vmin()
|
|
82
|
+
old_vmax = self.get_vmax()
|
|
83
|
+
new_vmax = simpledialog.askfloat("Set vmax", "Enter new vmax:", initialvalue=old_vmax)
|
|
84
|
+
if new_vmax is None:
|
|
85
|
+
return
|
|
86
|
+
if new_vmax <= old_vmin:
|
|
87
|
+
print("Error: vmax must be greater than vmin.")
|
|
88
|
+
return
|
|
89
|
+
self.vmax_button.config(text=f"vmax: {new_vmax:.2f}")
|
|
90
|
+
self.image.set_clim(vmax=new_vmax)
|
|
91
|
+
if hasattr(self, 'image_aux'):
|
|
92
|
+
self.image_aux.set_clim(vmax=new_vmax)
|
|
93
|
+
self.canvas.draw_idle()
|
|
94
|
+
|
|
95
|
+
def get_vmin(self):
|
|
96
|
+
"""Get the current minimum display value (vmin)."""
|
|
97
|
+
return float(self.vmin_button.cget("text").split(":")[1])
|
|
98
|
+
|
|
99
|
+
def get_vmax(self):
|
|
100
|
+
"""Get the current maximum display value (vmax)."""
|
|
101
|
+
return float(self.vmax_button.cget("text").split(":")[1])
|
|
102
|
+
|
|
103
|
+
def get_displayed_region(self):
|
|
104
|
+
"""Get the currently displayed region of the image."""
|
|
105
|
+
if hasattr(self, 'ax'):
|
|
106
|
+
xmin, xmax = self.ax.get_xlim()
|
|
107
|
+
xmin = int(xmin + 0.5)
|
|
108
|
+
if xmin < 1:
|
|
109
|
+
xmin = 1
|
|
110
|
+
xmax = int(xmax + 0.5)
|
|
111
|
+
if xmax > self.data.shape[1]:
|
|
112
|
+
xmax = self.data.shape[1]
|
|
113
|
+
ymin, ymax = self.ax.get_ylim()
|
|
114
|
+
ymin = int(ymin + 0.5)
|
|
115
|
+
if ymin < 1:
|
|
116
|
+
ymin = 1
|
|
117
|
+
ymax = int(ymax + 0.5)
|
|
118
|
+
if ymax > self.data.shape[0]:
|
|
119
|
+
ymax = self.data.shape[0]
|
|
120
|
+
print(f"Setting min/max using axis limits: x=({xmin:.2f}, {xmax:.2f}), y=({ymin:.2f}, {ymax:.2f})")
|
|
121
|
+
region = self.region = SliceRegion2D(
|
|
122
|
+
f'[{xmin}:{xmax}, {ymin}:{ymax}]', mode='fits'
|
|
123
|
+
).python
|
|
124
|
+
elif hasattr(self, 'region'):
|
|
125
|
+
region = self.region
|
|
126
|
+
else:
|
|
127
|
+
raise AttributeError("No axis or region defined for set_minmax.")
|
|
128
|
+
return region
|
|
129
|
+
|
|
130
|
+
def set_minmax(self):
|
|
131
|
+
"""Set vmin and vmax based on the currently displayed region."""
|
|
132
|
+
region = self.get_displayed_region()
|
|
133
|
+
vmin_new = np.min(self.data[region])
|
|
134
|
+
vmax_new = np.max(self.data[region])
|
|
135
|
+
self.vmin_button.config(text=f"vmin: {vmin_new:.2f}")
|
|
136
|
+
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
137
|
+
self.image.set_clim(vmin=vmin_new)
|
|
138
|
+
self.image.set_clim(vmax=vmax_new)
|
|
139
|
+
if hasattr(self, 'image_aux'):
|
|
140
|
+
self.image_aux.set_clim(vmin=vmin_new)
|
|
141
|
+
self.image_aux.set_clim(vmax=vmax_new)
|
|
142
|
+
self.canvas.draw_idle()
|
|
143
|
+
|
|
144
|
+
def set_zscale(self):
|
|
145
|
+
"""Set vmin and vmax using zscale on the currently displayed region."""
|
|
146
|
+
region = self.get_displayed_region()
|
|
147
|
+
vmin_new, vmax_new = zscale(self.data[region])
|
|
148
|
+
self.vmin_button.config(text=f"vmin: {vmin_new:.2f}")
|
|
149
|
+
self.vmax_button.config(text=f"vmax: {vmax_new:.2f}")
|
|
150
|
+
self.image.set_clim(vmin=vmin_new)
|
|
151
|
+
self.image.set_clim(vmax=vmax_new)
|
|
152
|
+
if hasattr(self, 'image_aux'):
|
|
153
|
+
self.image_aux.set_clim(vmin=vmin_new)
|
|
154
|
+
self.image_aux.set_clim(vmax=vmax_new)
|
|
155
|
+
self.canvas.draw_idle()
|
|
@@ -0,0 +1,83 @@
|
|
|
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
|
+
"""Surface interpolation (plane fit) or median interpolation"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
from scipy.ndimage import binary_dilation
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
def interpolation_a(data, mask_fixed, cr_labels, cr_index, npoints, method):
|
|
17
|
+
"""Interpolate cosmic ray pixels using surface fit or median of border pixels.
|
|
18
|
+
|
|
19
|
+
Parameters
|
|
20
|
+
----------
|
|
21
|
+
data : 2D numpy.ndarray
|
|
22
|
+
The image data array where cosmic rays are to be interpolated.
|
|
23
|
+
mask_fixed : 2D numpy.ndarray of bool
|
|
24
|
+
A boolean mask array indicating which pixels have been fixed.
|
|
25
|
+
cr_labels : 2D numpy.ndarray
|
|
26
|
+
An array labeling cosmic ray features.
|
|
27
|
+
cr_index : int
|
|
28
|
+
The index of the current cosmic ray feature to interpolate.
|
|
29
|
+
npoints : int
|
|
30
|
+
The number of points to use for interpolation.
|
|
31
|
+
method : str
|
|
32
|
+
The interpolation method to use ('surface' or 'median').
|
|
33
|
+
|
|
34
|
+
Returns
|
|
35
|
+
-------
|
|
36
|
+
interpolation_performed : bool
|
|
37
|
+
True if interpolation was performed, False otherwise.
|
|
38
|
+
xfit_all : list
|
|
39
|
+
X-coordinates of border pixels used for interpolation.
|
|
40
|
+
yfit_all : list
|
|
41
|
+
Y-coordinates of border pixels used for interpolation.
|
|
42
|
+
"""
|
|
43
|
+
# Mask of CR pixels
|
|
44
|
+
mask = (cr_labels == cr_index)
|
|
45
|
+
# Dilate the mask to find border pixels
|
|
46
|
+
dilated_mask = binary_dilation(mask, structure=np.ones((3, 3)), iterations=npoints)
|
|
47
|
+
# Border pixels are those in the dilated mask but not in the original mask
|
|
48
|
+
border_mask = dilated_mask & (~mask)
|
|
49
|
+
# Get coordinates of border pixels
|
|
50
|
+
yfit_all, xfit_all = np.where(border_mask)
|
|
51
|
+
zfit_all = data[yfit_all, xfit_all].tolist()
|
|
52
|
+
# Perform interpolation
|
|
53
|
+
interpolation_performed = False
|
|
54
|
+
if method == 'surface':
|
|
55
|
+
if len(xfit_all) > 3:
|
|
56
|
+
# Construct the design matrix for a 2D polynomial fit to a plane,
|
|
57
|
+
# where each row corresponds to a point (x, y, z) and the model
|
|
58
|
+
# is z = C[0]*x + C[1]*y + C[2]
|
|
59
|
+
A = np.c_[xfit_all, yfit_all, np.ones(len(xfit_all))]
|
|
60
|
+
# Least squares polynomial fit
|
|
61
|
+
C, _, _, _ = np.linalg.lstsq(A, zfit_all, rcond=None)
|
|
62
|
+
# recompute all CR pixels to take into account "holes" between marked pixels
|
|
63
|
+
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
64
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
65
|
+
data[iy, ix] = C[0] * ix + C[1] * iy + C[2]
|
|
66
|
+
mask_fixed[iy, ix] = True
|
|
67
|
+
interpolation_performed = True
|
|
68
|
+
else:
|
|
69
|
+
print("Not enough points to fit a plane")
|
|
70
|
+
elif method == 'median':
|
|
71
|
+
# Compute median of all surrounding points
|
|
72
|
+
if len(zfit_all) > 0:
|
|
73
|
+
zmed = np.median(zfit_all)
|
|
74
|
+
# recompute all CR pixels to take into account "holes" between marked pixels
|
|
75
|
+
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
76
|
+
for iy, ix in zip(ycr_list, xcr_list):
|
|
77
|
+
data[iy, ix] = zmed
|
|
78
|
+
mask_fixed[iy, ix] = True
|
|
79
|
+
interpolation_performed = True
|
|
80
|
+
else:
|
|
81
|
+
print(f"Unknown interpolation method: {method}")
|
|
82
|
+
|
|
83
|
+
return interpolation_performed, xfit_all, yfit_all
|
|
@@ -0,0 +1,80 @@
|
|
|
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
|
+
"""Polynomial nterpolation in the X direction"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def interpolation_x(data, mask_fixed, cr_labels, cr_index, npoints, degree):
|
|
16
|
+
"""Interpolate cosmic ray affected pixels in the X direction.
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
data : 2D numpy.ndarray
|
|
20
|
+
The image data array to be processed.
|
|
21
|
+
mask_fixed : 2D numpy.ndarray of bool
|
|
22
|
+
A boolean mask array indicating which pixels have been fixed.
|
|
23
|
+
cr_labels : 2D numpy.ndarray
|
|
24
|
+
An array labeling cosmic ray features.
|
|
25
|
+
cr_index : int
|
|
26
|
+
The index of the current cosmic ray feature to interpolate.
|
|
27
|
+
npoints : int
|
|
28
|
+
The number of points to use for interpolation.
|
|
29
|
+
degree : int
|
|
30
|
+
The degree of the polynomial to fit.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
interpolation_performed : bool
|
|
35
|
+
True if interpolation was performed, False otherwise.
|
|
36
|
+
xfit_all : list
|
|
37
|
+
X-coordinates of border pixels used for interpolation.
|
|
38
|
+
yfit_all : list
|
|
39
|
+
Y-coordinates of border pixels used for interpolation.
|
|
40
|
+
"""
|
|
41
|
+
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
42
|
+
ycr_min = np.min(ycr_list)
|
|
43
|
+
ycr_max = np.max(ycr_list)
|
|
44
|
+
xfit_all = []
|
|
45
|
+
yfit_all = []
|
|
46
|
+
interpolation_performed = False
|
|
47
|
+
for ycr in range(ycr_min, ycr_max + 1):
|
|
48
|
+
xmarked = xcr_list[np.where(ycr_list == ycr)]
|
|
49
|
+
if len(xmarked) > 0:
|
|
50
|
+
jmin = np.min(xmarked)
|
|
51
|
+
jmax = np.max(xmarked)
|
|
52
|
+
# mark intermediate pixels too
|
|
53
|
+
for ix in range(jmin, jmax + 1):
|
|
54
|
+
cr_labels[ycr, ix] = cr_index
|
|
55
|
+
xmarked = xcr_list[np.where(ycr_list == ycr)]
|
|
56
|
+
xfit = []
|
|
57
|
+
zfit = []
|
|
58
|
+
for i in range(jmin - npoints, jmin):
|
|
59
|
+
if 0 <= i < data.shape[1]:
|
|
60
|
+
xfit.append(i)
|
|
61
|
+
xfit_all.append(i)
|
|
62
|
+
yfit_all.append(ycr)
|
|
63
|
+
zfit.append(data[ycr, i])
|
|
64
|
+
for i in range(jmax + 1, jmax + 1 + npoints):
|
|
65
|
+
if 0 <= i < data.shape[1]:
|
|
66
|
+
xfit.append(i)
|
|
67
|
+
xfit_all.append(i)
|
|
68
|
+
yfit_all.append(ycr)
|
|
69
|
+
zfit.append(data[ycr, i])
|
|
70
|
+
if len(xfit) > degree:
|
|
71
|
+
p = np.polyfit(xfit, zfit, degree)
|
|
72
|
+
for i in range(jmin, jmax + 1):
|
|
73
|
+
if 0 <= i < data.shape[1]:
|
|
74
|
+
data[ycr, i] = np.polyval(p, i)
|
|
75
|
+
mask_fixed[ycr, i] = True
|
|
76
|
+
interpolation_performed = True
|
|
77
|
+
else:
|
|
78
|
+
print(f"Not enough points to fit at y={ycr+1}")
|
|
79
|
+
|
|
80
|
+
return interpolation_performed, xfit_all, yfit_all
|
|
@@ -0,0 +1,81 @@
|
|
|
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
|
+
"""Polynomial nterpolation in the Y direction"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def interpolation_y(data, mask_fixed, cr_labels, cr_index, npoints, degree):
|
|
16
|
+
"""Interpolate cosmic ray affected pixels in Y direction.
|
|
17
|
+
Parameters
|
|
18
|
+
----------
|
|
19
|
+
data : 2D numpy.ndarray
|
|
20
|
+
The image data array to be processed.
|
|
21
|
+
mask_fixed : 2D numpy.ndarray of bool
|
|
22
|
+
A boolean mask array indicating which pixels have been fixed.
|
|
23
|
+
cr_labels : 2D numpy.ndarray
|
|
24
|
+
An array labeling cosmic ray features.
|
|
25
|
+
cr_index : int
|
|
26
|
+
The index of the current cosmic ray feature to interpolate.
|
|
27
|
+
npoints : int
|
|
28
|
+
The number of points to use for interpolation.
|
|
29
|
+
degree : int
|
|
30
|
+
The degree of the polynomial to fit.
|
|
31
|
+
|
|
32
|
+
Returns
|
|
33
|
+
-------
|
|
34
|
+
interpolation_performed : bool
|
|
35
|
+
True if interpolation was performed, False otherwise.
|
|
36
|
+
xfit_all : list
|
|
37
|
+
X-coordinates of border pixels used for interpolation.
|
|
38
|
+
yfit_all : list
|
|
39
|
+
Y-coordinates of border pixels used for interpolation.
|
|
40
|
+
"""
|
|
41
|
+
ycr_list, xcr_list = np.where(cr_labels == cr_index)
|
|
42
|
+
xcr_min = np.min(xcr_list)
|
|
43
|
+
xcr_max = np.max(xcr_list)
|
|
44
|
+
xfit_all = []
|
|
45
|
+
yfit_all = []
|
|
46
|
+
interpolation_performed = False
|
|
47
|
+
for xcr in range(xcr_min, xcr_max + 1):
|
|
48
|
+
ymarked = ycr_list[np.where(xcr_list == xcr)]
|
|
49
|
+
if len(ymarked) > 0:
|
|
50
|
+
imin = np.min(ymarked)
|
|
51
|
+
imax = np.max(ymarked)
|
|
52
|
+
# mark intermediate pixels too
|
|
53
|
+
for iy in range(imin, imax + 1):
|
|
54
|
+
cr_labels[iy, xcr] = cr_index
|
|
55
|
+
ymarked = ycr_list[np.where(xcr_list == xcr)]
|
|
56
|
+
yfit = []
|
|
57
|
+
zfit = []
|
|
58
|
+
for i in range(imin - npoints, imin):
|
|
59
|
+
if 0 <= i < data.shape[0]:
|
|
60
|
+
yfit.append(i)
|
|
61
|
+
yfit_all.append(i)
|
|
62
|
+
xfit_all.append(xcr)
|
|
63
|
+
zfit.append(data[i, xcr])
|
|
64
|
+
for i in range(imax + 1, imax + 1 + npoints):
|
|
65
|
+
if 0 <= i < data.shape[0]:
|
|
66
|
+
yfit.append(i)
|
|
67
|
+
yfit_all.append(i)
|
|
68
|
+
xfit_all.append(xcr)
|
|
69
|
+
zfit.append(data[i, xcr])
|
|
70
|
+
if len(yfit) > degree:
|
|
71
|
+
p = np.polyfit(yfit, zfit, degree)
|
|
72
|
+
for i in range(imin, imax + 1):
|
|
73
|
+
if 0 <= i < data.shape[1]:
|
|
74
|
+
data[i, xcr] = np.polyval(p, i)
|
|
75
|
+
mask_fixed[i, xcr] = True
|
|
76
|
+
interpolation_performed = True
|
|
77
|
+
else:
|
|
78
|
+
print(f"Not enough points to fit at x={xcr+1}")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
return interpolation_performed, xfit_all, yfit_all
|