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.
@@ -0,0 +1,207 @@
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
+ """Interpolation editor dialog for interpolation parameters."""
11
+
12
+ import tkinter as tk
13
+ from tkinter import messagebox
14
+
15
+ from .definitions import VALID_CLEANING_METHODS
16
+
17
+
18
+ class InterpolationEditor:
19
+ """Dialog to select interpolation cleaning parameters."""
20
+ def __init__(self, root, last_dilation, last_npoints, last_degree, auxdata):
21
+ """Initialize the interpolation editor dialog.
22
+
23
+ Parameters
24
+ ----------
25
+ root : tk.Tk
26
+ The root Tkinter window.
27
+ last_dilation : int
28
+ The last used dilation parameter.
29
+ last_npoints : int
30
+ The last used number of points for interpolation.
31
+ last_degree : int
32
+ The last used degree for interpolation.
33
+ auxdata : array-like or None
34
+ Auxiliary data for cleaning, if available.
35
+
36
+ Methods
37
+ -------
38
+ create_widgets()
39
+ Create the widgets for the dialog.
40
+ on_ok()
41
+ Handle the OK button click event.
42
+ on_cancel()
43
+ Handle the Cancel button click event.
44
+ action_on_method_change()
45
+ Handle changes in the selected cleaning method.
46
+ check_interp_methods()
47
+ Check that all interpolation methods are valid.
48
+
49
+ Attributes
50
+ ----------
51
+ root : tk.Tk
52
+ The root Tkinter window.
53
+ last_dilation : int
54
+ The last used dilation parameter.
55
+ auxdata : array-like or None
56
+ Auxiliary data for cleaning, if available.
57
+ dict_interp_methods : dict
58
+ Mapping of interpolation method names to their codes.
59
+ cleaning_method : str or None
60
+ The selected cleaning method code.
61
+ npoints : int
62
+ The number of points for interpolation.
63
+ degree : int
64
+ The degree for interpolation.
65
+ """
66
+ self.root = root
67
+ self.root.title("Cleaning Parameters")
68
+ self.last_dilation = last_dilation
69
+ self.auxdata = auxdata
70
+ self.dict_interp_methods = {
71
+ "x interp.": "x",
72
+ "y interp.": "y",
73
+ "surface interp.": "a-plane",
74
+ "median": "a-median",
75
+ "lacosmic": "lacosmic",
76
+ "auxdata": "auxdata"
77
+ }
78
+ self.check_interp_methods()
79
+ # Initialize parameters
80
+ self.cleaning_method = None
81
+ self.npoints = last_npoints
82
+ self.degree = last_degree
83
+ # Create the form
84
+ self.create_widgets()
85
+
86
+ def create_widgets(self):
87
+ """Create the widgets for the dialog."""
88
+ # Main frame
89
+ main_frame = tk.Frame(self.root, padx=10, pady=10)
90
+ main_frame.pack()
91
+
92
+ # Title
93
+ title_label = tk.Label(main_frame, text="Select Cleaning Method", font=("Arial", 14, "bold"))
94
+ title_label.grid(row=0, column=0, columnspan=2, pady=(0, 15))
95
+
96
+ # Create labels and entry fields for each cleaning method
97
+ row = 1
98
+ self.cleaning_method_var = tk.StringVar(value="surface interp.")
99
+ for interp_method in self.dict_interp_methods.keys():
100
+ valid_method = True
101
+ # Skip replace by L.A.Cosmic values if last dilation is not zero
102
+ if interp_method == "lacosmic" and self.last_dilation != 0:
103
+ valid_method = False
104
+ # Skip auxdata method if auxdata is not available
105
+ if interp_method == "auxdata" and self.auxdata is None:
106
+ valid_method = False
107
+ if valid_method:
108
+ tk.Radiobutton(
109
+ main_frame,
110
+ text=interp_method,
111
+ variable=self.cleaning_method_var,
112
+ value=interp_method,
113
+ command=self.action_on_method_change
114
+ ).grid(row=row, column=0, sticky='w')
115
+ row += 1
116
+
117
+ # Create labels and entry fields for each additional parameter
118
+ label = tk.Label(main_frame, text='Npoints:')
119
+ label.grid(row=row, column=0, sticky='e', padx=(0, 10))
120
+ self.entry_npoints = tk.Entry(main_frame, width=10)
121
+ self.entry_npoints.insert(0, self.npoints)
122
+ self.entry_npoints.grid(row=row, column=1, sticky='w')
123
+ row += 1
124
+ label = tk.Label(main_frame, text='Degree:')
125
+ label.grid(row=row, column=0, sticky='e', padx=(0, 10))
126
+ self.entry_degree = tk.Entry(main_frame, width=10)
127
+ self.entry_degree.insert(0, self.degree)
128
+ self.entry_degree.grid(row=row, column=1, sticky='w')
129
+ row += 1
130
+
131
+ # Button frame
132
+ self.button_frame = tk.Frame(main_frame)
133
+ self.button_frame.grid(row=row, column=0, columnspan=2, pady=(15, 0))
134
+
135
+ # OK button
136
+ self.ok_button = tk.Button(self.button_frame, text="OK", width=5, command=self.on_ok)
137
+ self.ok_button.pack(side='left', padx=5)
138
+
139
+ # Cancel button
140
+ self.cancel_button = tk.Button(self.button_frame, text="Cancel", width=5, command=self.on_cancel)
141
+ self.cancel_button.pack(side='left', padx=5)
142
+
143
+ # Initial action depending on the default method
144
+ self.action_on_method_change()
145
+
146
+ def on_ok(self):
147
+ """Handle the OK button click event."""
148
+ self.cleaning_method = self.dict_interp_methods[self.cleaning_method_var.get()]
149
+ try:
150
+ self.npoints = int(self.entry_npoints.get())
151
+ except ValueError:
152
+ messagebox.showerror("Input Error", "Npoints must be a positive integer.")
153
+ return
154
+ if self.npoints < 1:
155
+ messagebox.showerror("Input Error", "Npoints must be at least 1.")
156
+ return
157
+
158
+ try:
159
+ self.degree = int(self.entry_degree.get())
160
+ except ValueError:
161
+ messagebox.showerror("Input Error", "Degree must be an integer.")
162
+ return
163
+ if self.degree < 0:
164
+ messagebox.showerror("Input Error", "Degree must be non-negative.")
165
+ return
166
+
167
+ if self.cleaning_method in ['x', 'y'] and 2 * self.npoints <= self.degree:
168
+ messagebox.showerror("Input Error", "2*Npoints must be greater than Degree for x and y interpolation.")
169
+ return
170
+
171
+ self.root.destroy()
172
+
173
+ def on_cancel(self):
174
+ """Close the dialog without saving selected parameters."""
175
+ self.cleaning_method = None
176
+ self.npoints = None
177
+ self.degree = None
178
+ self.root.destroy()
179
+
180
+ def action_on_method_change(self):
181
+ """Handle changes in the selected cleaning method."""
182
+ selected_method = self.cleaning_method_var.get()
183
+ print(f"Selected cleaning method: {selected_method}")
184
+ if selected_method in ['x interp.', 'y interp.']:
185
+ self.entry_npoints.config(state='normal')
186
+ self.entry_degree.config(state='normal')
187
+ elif selected_method == 'surface interp.':
188
+ self.entry_npoints.config(state='normal')
189
+ self.entry_degree.config(state='disabled')
190
+ elif selected_method == 'median':
191
+ self.entry_npoints.config(state='normal')
192
+ self.entry_degree.config(state='disabled')
193
+ elif selected_method == 'lacosmic':
194
+ self.entry_npoints.config(state='disabled')
195
+ self.entry_degree.config(state='disabled')
196
+ elif selected_method == 'auxdata':
197
+ self.entry_npoints.config(state='disabled')
198
+ self.entry_degree.config(state='disabled')
199
+
200
+ def check_interp_methods(self):
201
+ """Check that all interpolation methods are valid."""
202
+ for method in self.dict_interp_methods.keys():
203
+ if method not in VALID_CLEANING_METHODS:
204
+ raise ValueError(f"Invalid interpolation method: {method}")
205
+ for method in VALID_CLEANING_METHODS:
206
+ if method not in self.dict_interp_methods.keys():
207
+ raise ValueError(f"Interpolation method not mapped: {method}")
@@ -0,0 +1,251 @@
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
+ """Parameter editor dialog for L.A.Cosmic parameters."""
11
+
12
+ import tkinter as tk
13
+ from tkinter import ttk
14
+ from tkinter import messagebox
15
+
16
+ from .definitions import lacosmic_default_dict
17
+
18
+
19
+ class ParameterEditor:
20
+ """A dialog to edit L.A.Cosmic parameters."""
21
+ def __init__(self, root, param_dict, window_title, xmin, xmax, ymin, ymax, imgshape):
22
+ """Initialize the parameter editor dialog.
23
+
24
+ Parameters
25
+ ----------
26
+ root : tk.Tk
27
+ The root Tkinter window.
28
+ param_dict : dict
29
+ Dictionary with L.A.Cosmic parameters.
30
+ window_title : str
31
+ Title of the dialog window.
32
+ xmin : int
33
+ Minimum x-coordinate of the region to be examined.
34
+ xmax : int
35
+ Maximum x-coordinate of the region to be examined.
36
+ ymin : int
37
+ Minimum y-coordinate of the region to be examined.
38
+ ymax : int
39
+ Maximum y-coordinate of the region to be examined.
40
+ imgshape : tuple
41
+ Shape of the image (height, width).
42
+
43
+ Methods
44
+ -------
45
+ create_widgets()
46
+ Create the widgets for the dialog.
47
+ on_ok()
48
+ Validate and save the updated values.
49
+ on_cancel()
50
+ Close the dialog without saving.
51
+ on_reset()
52
+ Reset all fields to original values.
53
+ get_result()
54
+ Return the updated dictionary.
55
+
56
+ Attributes
57
+ ----------
58
+ root : tk.Tk
59
+ The root Tkinter window.
60
+ param_dict : dict
61
+ Dictionary with L.A.Cosmic parameters.
62
+ imgshape : tuple
63
+ Shape of the image (height, width).
64
+ entries : dict
65
+ Dictionary to hold entry widgets.
66
+ result_dict : dict or None
67
+ The updated dictionary of parameters or None if cancelled.
68
+ """
69
+ self.root = root
70
+ self.root.title(window_title)
71
+ self.param_dict = param_dict
72
+ # Set default region values
73
+ self.param_dict['xmin']['value'] = xmin
74
+ self.param_dict['xmax']['value'] = xmax
75
+ self.param_dict['ymin']['value'] = ymin
76
+ self.param_dict['ymax']['value'] = ymax
77
+ self.imgshape = imgshape
78
+ self.entries = {} # dictionary to hold entry widgets
79
+ self.result_dict = None
80
+
81
+ # Create the form
82
+ self.create_widgets()
83
+
84
+ def create_widgets(self):
85
+ """Create the widgets for the dialog."""
86
+ # Main frame
87
+ main_frame = tk.Frame(self.root, padx=10, pady=10)
88
+ main_frame.pack()
89
+
90
+ row = 0
91
+
92
+ # Subtitle for L.A.Cosmic parameters
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=3, pady=(0, 15))
95
+ row += 1
96
+
97
+ # Create labels and entry fields for each parameter
98
+ for key, info in self.param_dict.items():
99
+ if key.lower() not in ['dilation', 'xmin', 'xmax', 'ymin', 'ymax']:
100
+ # Parameter name label
101
+ label = tk.Label(main_frame, text=f"{key}:", anchor='e', width=15)
102
+ label.grid(row=row, column=0, sticky='w', pady=5)
103
+ # Entry field
104
+ entry = tk.Entry(main_frame, width=10)
105
+ entry.insert(0, str(info['value']))
106
+ entry.grid(row=row, column=1, padx=10, pady=5)
107
+ self.entries[key] = entry # dictionary to hold entry widgets
108
+ # Type label
109
+ type_label = tk.Label(main_frame, text=f"({info['type'].__name__})", fg='gray', anchor='w', width=10)
110
+ type_label.grid(row=row, column=2, sticky='w', pady=5)
111
+ row += 1
112
+
113
+ # Separator
114
+ separator1 = ttk.Separator(main_frame, orient='horizontal')
115
+ separator1.grid(row=row, column=0, columnspan=3, sticky='ew', pady=(10, 10))
116
+ row += 1
117
+
118
+ # Subtitle for additional parameters
119
+ subtitle_label = tk.Label(main_frame, text="Additional Parameters", font=("Arial", 14, "bold"))
120
+ subtitle_label.grid(row=row, column=0, columnspan=3, pady=(0, 15))
121
+ row += 1
122
+
123
+ # Dilation label and entry
124
+ label = tk.Label(main_frame, text="Dilation:", anchor='e', width=15)
125
+ label.grid(row=row, column=0, sticky='w', pady=5)
126
+ entry = tk.Entry(main_frame, width=10)
127
+ entry.insert(0, str(self.param_dict['dilation']['value']))
128
+ entry.grid(row=row, column=1, padx=10, pady=5)
129
+ self.entries['dilation'] = entry
130
+ type_label = tk.Label(main_frame, text=f"({self.param_dict['dilation']['type'].__name__})",
131
+ fg='gray', anchor='w', width=10)
132
+ type_label.grid(row=row, column=2, sticky='w', pady=5)
133
+ row += 1
134
+
135
+ # Separator
136
+ separator2 = ttk.Separator(main_frame, orient='horizontal')
137
+ separator2.grid(row=row, column=0, columnspan=3, sticky='ew', pady=(10, 10))
138
+ row += 1
139
+
140
+ # Subtitle for region to be examined
141
+ subtitle_label = tk.Label(main_frame, text="Region to be Examined", font=("Arial", 14, "bold"))
142
+ subtitle_label.grid(row=row, column=0, columnspan=3, pady=(0, 15))
143
+ row += 1
144
+
145
+ # Region to be examined label and entries
146
+ for key, info in self.param_dict.items():
147
+ if key.lower() in ['xmin', 'xmax', 'ymin', 'ymax']:
148
+ # Parameter name label
149
+ label = tk.Label(main_frame, text=f"{key}:", anchor='e', width=15)
150
+ label.grid(row=row, column=0, sticky='w', pady=5)
151
+ # Entry field
152
+ entry = tk.Entry(main_frame, width=10)
153
+ entry.insert(0, str(info['value']))
154
+ entry.grid(row=row, column=1, padx=10, pady=5)
155
+ self.entries[key] = entry # dictionary to hold entry widgets
156
+ # Type label
157
+ dumtext = f"({info['type'].__name__})"
158
+ if key.lower() in ['xmax', 'ymax']:
159
+ dumtext += f" --> [1, {self.imgshape[1]}]"
160
+ else:
161
+ dumtext += f" --> [1, {self.imgshape[0]}]"
162
+ type_label = tk.Label(main_frame, text=dumtext, fg='gray', anchor='w', width=15)
163
+ type_label.grid(row=row, column=2, sticky='w', pady=5)
164
+ row += 1
165
+
166
+ # Separator
167
+ separator3 = ttk.Separator(main_frame, orient='horizontal')
168
+ separator3.grid(row=row, column=0, columnspan=3, sticky='ew', pady=(10, 10))
169
+ row += 1
170
+
171
+ # Button frame
172
+ button_frame = tk.Frame(main_frame)
173
+ button_frame.grid(row=row, column=0, columnspan=3, pady=(5, 0))
174
+
175
+ # OK button
176
+ ok_button = tk.Button(button_frame, text="OK", width=5, command=self.on_ok)
177
+ ok_button.pack(side='left', padx=5)
178
+
179
+ # Cancel button
180
+ cancel_button = tk.Button(button_frame, text="Cancel", width=5, command=self.on_cancel)
181
+ cancel_button.pack(side='left', padx=5)
182
+
183
+ # Reset button
184
+ reset_button = tk.Button(button_frame, text="Reset", width=5, command=self.on_reset)
185
+ reset_button.pack(side='left', padx=5)
186
+
187
+ def on_ok(self):
188
+ """Validate and save the updated values"""
189
+ try:
190
+ updated_dict = {}
191
+
192
+ for key, info in self.param_dict.items():
193
+ entry_value = self.entries[key].get()
194
+ value_type = info['type']
195
+
196
+ # Convert string to appropriate type
197
+ if value_type == bool:
198
+ # Handle boolean conversion
199
+ if entry_value.lower() in ['true', '1', 'yes']:
200
+ converted_value = True
201
+ elif entry_value.lower() in ['false', '0', 'no']:
202
+ converted_value = False
203
+ else:
204
+ raise ValueError(f"Invalid boolean value for {key}")
205
+ else:
206
+ converted_value = value_type(entry_value)
207
+ if 'positive' in info and info['positive'] and converted_value < 0:
208
+ raise ValueError(f"Value for {key} must be positive")
209
+
210
+ updated_dict[key] = {
211
+ 'value': converted_value,
212
+ 'type': value_type
213
+ }
214
+
215
+ # Additional validation for region limits
216
+ try:
217
+ if updated_dict['xmax']['value'] <= updated_dict['xmin']['value']:
218
+ raise ValueError("xmax must be greater than xmin")
219
+ if updated_dict['ymax']['value'] <= updated_dict['ymin']['value']:
220
+ raise ValueError("ymax must be greater than ymin")
221
+ self.result_dict = updated_dict
222
+ self.root.destroy()
223
+ except ValueError as e:
224
+ messagebox.showerror("Invalid Inputs",
225
+ "Error in region limits:\n"
226
+ f"{str(e)}\n\nPlease check your inputs.")
227
+
228
+ except ValueError as e:
229
+ messagebox.showerror("Invalid Inputs",
230
+ f"Error converting value for {key}:\n{str(e)}\n\n"
231
+ "Please check your inputs.")
232
+
233
+ def on_cancel(self):
234
+ """Close without saving"""
235
+ self.result_dict = None
236
+ self.root.destroy()
237
+
238
+ def on_reset(self):
239
+ """Reset all fields to original values"""
240
+ self.param_dict = lacosmic_default_dict.copy()
241
+ self.param_dict['xmin']['value'] = 1
242
+ self.param_dict['xmax']['value'] = self.imgshape[1]
243
+ self.param_dict['ymin']['value'] = 1
244
+ self.param_dict['ymax']['value'] = self.imgshape[0]
245
+ for key, info in self.param_dict.items():
246
+ self.entries[key].delete(0, tk.END)
247
+ self.entries[key].insert(0, str(info['value']))
248
+
249
+ def get_result(self):
250
+ """Return the updated dictionary"""
251
+ return self.result_dict