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.
@@ -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
- '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},
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
- '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},
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
- 'dilation': {'value': 0, 'type': int, 'positive': True},
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
- 'xmin': {'value': 1, 'type': int, 'positive': True},
37
- 'xmax': {'value': None, 'type': int, 'positive': True},
38
- 'ymin': {'value': 1, 'type': int, 'positive': True},
39
- 'ymax': {'value': None, 'type': int, 'positive': True},
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
- 'nruns': {'value': 1, 'type': int, 'positive': True}
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, 'image_aux'):
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, 'image_aux'):
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, 'ax'):
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
- f'[{xmin}:{xmax}, {ymin}:{ymax}]', mode='fits'
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, 'image_aux'):
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, 'image_aux'):
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 cleanest(data, mask_crfound, dilation=0,
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 affected by
35
- cosmic rays.
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 ['x', 'y', 's', 'd', 'm']:
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 ['x', 'y']:
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
- 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}")
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 ['x', 'y']:
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 == 'x':
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 ['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'
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 = (cr_labels == cr_index)
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 == 'surface':
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 ['median', 'mean']:
83
+ elif method in ["median", "mean"]:
88
84
  # Compute median of all surrounding points
89
85
  if len(zfit_all) > 0:
90
- if method == 'median':
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
- def __init__(self, root, last_dilation, last_npoints, last_degree, auxdata):
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
- # Title
94
- title_label = tk.Label(main_frame, text="Select Cleaning Method", font=("Arial", 14, "bold"))
95
- title_label.grid(row=0, column=0, columnspan=2, pady=(0, 15))
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=0, sticky='w')
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='Npoints:')
120
- label.grid(row=row, column=0, sticky='e', padx=(0, 10))
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='w')
170
+ self.entry_npoints.grid(row=row, column=1, sticky="w")
124
171
  row += 1
125
- label = tk.Label(main_frame, text='Degree:')
126
- label.grid(row=row, column=0, sticky='e', padx=(0, 10))
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='w')
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=2, pady=(15, 0))
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='left', padx=5)
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='left', padx=5)
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 ['x', 'y'] and 2 * self.npoints <= self.degree:
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 ['x interp.', 'y interp.']:
186
- self.entry_npoints.config(state='normal')
187
- self.entry_degree.config(state='normal')
188
- elif selected_method == 'surface interp.':
189
- self.entry_npoints.config(state='normal')
190
- self.entry_degree.config(state='disabled')
191
- elif selected_method == 'median':
192
- self.entry_npoints.config(state='normal')
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')
197
- elif selected_method == 'lacosmic':
198
- self.entry_npoints.config(state='disabled')
199
- self.entry_degree.config(state='disabled')
200
- elif selected_method == 'auxdata':
201
- self.entry_npoints.config(state='disabled')
202
- self.entry_degree.config(state='disabled')
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