teareduce 0.4.5__py3-none-any.whl → 0.4.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.py +694 -0
- teareduce/ctext.py +3 -4
- teareduce/draw_rectangle.py +5 -5
- teareduce/imshow.py +23 -12
- teareduce/peaks_spectrum.py +4 -4
- teareduce/sliceregion.py +76 -5
- teareduce/statsummary.py +5 -5
- teareduce/tests/test_sliceregion.py +67 -0
- teareduce/version.py +1 -1
- teareduce/wavecal.py +28 -31
- teareduce/zscale.py +4 -4
- {teareduce-0.4.5.dist-info → teareduce-0.4.7.dist-info}/METADATA +1 -1
- teareduce-0.4.7.dist-info/RECORD +31 -0
- teareduce-0.4.7.dist-info/entry_points.txt +2 -0
- teareduce-0.4.5.dist-info/RECORD +0 -29
- {teareduce-0.4.5.dist-info → teareduce-0.4.7.dist-info}/WHEEL +0 -0
- {teareduce-0.4.5.dist-info → teareduce-0.4.7.dist-info}/licenses/LICENSE.txt +0 -0
- {teareduce-0.4.5.dist-info → teareduce-0.4.7.dist-info}/top_level.txt +0 -0
teareduce/ctext.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2022-
|
|
2
|
+
# Copyright 2022-2025 Universidad Complutense de Madrid
|
|
3
3
|
#
|
|
4
4
|
# This file is part of teareduce
|
|
5
5
|
#
|
|
@@ -9,8 +9,8 @@
|
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
def ctext(s=None,
|
|
12
|
-
fg=None,
|
|
13
|
-
bg=None,
|
|
12
|
+
fg=None,
|
|
13
|
+
bg=None,
|
|
14
14
|
under=False,
|
|
15
15
|
rev=False,
|
|
16
16
|
faint=False,
|
|
@@ -95,4 +95,3 @@ def ctext(s=None,
|
|
|
95
95
|
result = f'{final_style}{s}\x1B[0m'
|
|
96
96
|
|
|
97
97
|
return result
|
|
98
|
-
|
teareduce/draw_rectangle.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2022-
|
|
2
|
+
# Copyright 2022-2025 Universidad Complutense de Madrid
|
|
3
3
|
#
|
|
4
4
|
# This file is part of teareduce
|
|
5
5
|
#
|
|
@@ -56,11 +56,11 @@ def draw_rectangle(ax, image_data, x1, x2, y1, y2,
|
|
|
56
56
|
ax.plot((x1, x2), (y2, y2), color, lw=1)
|
|
57
57
|
|
|
58
58
|
if text:
|
|
59
|
-
ax.text((x1+x2)/2, y1+(y2-y1)/8,
|
|
60
|
-
'{:.{prec}f}'.format(mean, prec=ndigits),
|
|
61
|
-
ha='center', va='center', color=color, fontsize=fontsize)
|
|
59
|
+
ax.text((x1+x2)/2, y1+(y2-y1)/8,
|
|
60
|
+
'{:.{prec}f}'.format(mean, prec=ndigits),
|
|
61
|
+
ha='center', va='center', color=color, fontsize=fontsize)
|
|
62
62
|
ax.text((x1+x2)/2, y2-(y2-y1)/8,
|
|
63
|
-
'{:.{prec}f}'.format(std, prec=ndigits),
|
|
63
|
+
'{:.{prec}f}'.format(std, prec=ndigits),
|
|
64
64
|
ha='center', va='top', color=color, fontsize=fontsize)
|
|
65
65
|
|
|
66
66
|
return mean, std
|
teareduce/imshow.py
CHANGED
|
@@ -41,7 +41,7 @@ def imshowme(data, **kwargs):
|
|
|
41
41
|
return fig, ax, img, cax, cbar
|
|
42
42
|
|
|
43
43
|
|
|
44
|
-
def imshow(fig=None, ax=None, data=None,
|
|
44
|
+
def imshow(fig=None, ax=None, data=None, ds9mode=False,
|
|
45
45
|
crpix1=1, crval1=None, cdelt1=None, cunit1=None, cunitx=Unit('Angstrom'),
|
|
46
46
|
xlabel=None, ylabel=None, title=None,
|
|
47
47
|
colorbar=True, cblabel='Number of counts',
|
|
@@ -60,6 +60,10 @@ def imshow(fig=None, ax=None, data=None,
|
|
|
60
60
|
Instance of Axes.
|
|
61
61
|
data : numpy array
|
|
62
62
|
2D array to be displayed.
|
|
63
|
+
ds9mode : bool
|
|
64
|
+
If True, the extent parameter is set to
|
|
65
|
+
[0.5, NAXIS1+0.5, 0.5, NAXIS2+0.5]
|
|
66
|
+
to mimic the DS9 display.
|
|
63
67
|
crpix1 : astropy.units.Quantity
|
|
64
68
|
Float number providing the CRPIX1 value: the reference pixel
|
|
65
69
|
for which CRVAL1 is given.
|
|
@@ -103,13 +107,6 @@ def imshow(fig=None, ax=None, data=None,
|
|
|
103
107
|
if not isinstance(ax, Axes):
|
|
104
108
|
raise ValueError("Unexpected 'ax' argument")
|
|
105
109
|
|
|
106
|
-
# default labels
|
|
107
|
-
if xlabel is None:
|
|
108
|
-
xlabel = 'X axis (array index)'
|
|
109
|
-
|
|
110
|
-
if ylabel is None:
|
|
111
|
-
ylabel = 'Y axis (array index)'
|
|
112
|
-
|
|
113
110
|
wavecalib = False
|
|
114
111
|
if crpix1 is not None and crval1 is not None and cdelt1 is not None and cunit1 is not None:
|
|
115
112
|
if 'extent' in kwargs:
|
|
@@ -129,11 +126,25 @@ def imshow(fig=None, ax=None, data=None,
|
|
|
129
126
|
aspect = 'auto'
|
|
130
127
|
wavecalib = True
|
|
131
128
|
else:
|
|
132
|
-
if
|
|
133
|
-
extent
|
|
134
|
-
|
|
129
|
+
if ds9mode:
|
|
130
|
+
if 'extent' in kwargs:
|
|
131
|
+
raise ValueError('extent parameter can not be used with ds9mode=True')
|
|
132
|
+
naxis2, naxis1 = data.shape
|
|
133
|
+
extent = [0.5, naxis1 + 0.5, 0.5, naxis2 + 0.5]
|
|
134
|
+
if xlabel is None:
|
|
135
|
+
xlabel = 'X pixel (from 1 to NAXIS1)'
|
|
136
|
+
if ylabel is None:
|
|
137
|
+
ylabel = 'Y pixel (from 1 to NAXIS2)'
|
|
135
138
|
else:
|
|
136
|
-
|
|
139
|
+
if xlabel is None:
|
|
140
|
+
xlabel = 'X axis (array index)'
|
|
141
|
+
if ylabel is None:
|
|
142
|
+
ylabel = 'Y axis (array index)'
|
|
143
|
+
if 'extent' in kwargs:
|
|
144
|
+
extent = kwargs['extent']
|
|
145
|
+
del kwargs['extent']
|
|
146
|
+
else:
|
|
147
|
+
extent = None
|
|
137
148
|
if 'aspect' in kwargs:
|
|
138
149
|
aspect = kwargs['aspect']
|
|
139
150
|
del kwargs['aspect']
|
teareduce/peaks_spectrum.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright 2015-
|
|
3
|
+
# Copyright 2015-2025 Universidad Complutense de Madrid
|
|
4
4
|
#
|
|
5
5
|
# This file is part of teareduce
|
|
6
6
|
#
|
|
@@ -58,12 +58,12 @@ def find_peaks_spectrum(sx, nwinwidth, deltaflux=0, threshold=0, debugplot=False
|
|
|
58
58
|
'pixels will be ignored')
|
|
59
59
|
|
|
60
60
|
xpeaks = [] # list to store the peaks
|
|
61
|
-
|
|
61
|
+
|
|
62
62
|
if sx_shape[0] < nwinwidth:
|
|
63
63
|
print('find_peaks_spectrum> sx shape......:', sx_shape)
|
|
64
64
|
print('find_peaks_spectrum> nwinwidth.....:', nwinwidth)
|
|
65
65
|
raise ValueError('sx.shape < nwinwidth')
|
|
66
|
-
|
|
66
|
+
|
|
67
67
|
i = nmed
|
|
68
68
|
while i < sx_shape[0] - nmed:
|
|
69
69
|
if sx[i] > threshold:
|
|
@@ -93,7 +93,7 @@ def find_peaks_spectrum(sx, nwinwidth, deltaflux=0, threshold=0, debugplot=False
|
|
|
93
93
|
i += 1
|
|
94
94
|
else:
|
|
95
95
|
i += 1
|
|
96
|
-
|
|
96
|
+
|
|
97
97
|
ixpeaks = np.array(xpeaks)
|
|
98
98
|
|
|
99
99
|
if debugplot:
|
teareduce/sliceregion.py
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
#
|
|
9
9
|
"""Auxiliary classes to handle slicing regions in 1D, 2D, and 3D.
|
|
10
10
|
|
|
11
|
-
These classes provide a way to define and manipulate slices in a
|
|
11
|
+
These classes provide a way to define and manipulate slices in a
|
|
12
12
|
consistent manner, following both FITS and Python conventions.
|
|
13
13
|
"""
|
|
14
14
|
|
|
@@ -16,9 +16,10 @@ import re
|
|
|
16
16
|
|
|
17
17
|
import numpy as np
|
|
18
18
|
|
|
19
|
+
|
|
19
20
|
class SliceRegion1D:
|
|
20
21
|
"""Store indices for slicing of 1D regions.
|
|
21
|
-
|
|
22
|
+
|
|
22
23
|
The attributes .python and .fits provide the indices following
|
|
23
24
|
the Python and the FITS convention, respectively.
|
|
24
25
|
|
|
@@ -42,7 +43,7 @@ class SliceRegion1D:
|
|
|
42
43
|
Check if slice 'other' is within the parent slice.
|
|
43
44
|
"""
|
|
44
45
|
|
|
45
|
-
def __init__(self, region, mode=None):
|
|
46
|
+
def __init__(self, region, mode=None, naxis1=None):
|
|
46
47
|
"""Initialize SliceRegion1D.
|
|
47
48
|
|
|
48
49
|
Parameters
|
|
@@ -53,6 +54,9 @@ class SliceRegion1D:
|
|
|
53
54
|
mode : str
|
|
54
55
|
Convention mode employed to define the slice.
|
|
55
56
|
The two possible modes are 'fits' and 'python'.
|
|
57
|
+
naxis1 : int
|
|
58
|
+
The axis 1 size (length) of the data being sliced.
|
|
59
|
+
If provided, it is used to validate the slice region.
|
|
56
60
|
"""
|
|
57
61
|
if isinstance(region, str):
|
|
58
62
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -92,6 +96,12 @@ class SliceRegion1D:
|
|
|
92
96
|
else:
|
|
93
97
|
raise ValueError(errmsg)
|
|
94
98
|
|
|
99
|
+
if naxis1 is not None:
|
|
100
|
+
if not (1 <= self.fits.start <= naxis1):
|
|
101
|
+
raise ValueError(f'Invalid start={self.fits.start} for naxis1={naxis1}')
|
|
102
|
+
if not (1 <= self.fits.stop <= naxis1+1):
|
|
103
|
+
raise ValueError(f'Invalid stop={self.fits.stop} for naxis1={naxis1}')
|
|
104
|
+
|
|
95
105
|
s = self.fits
|
|
96
106
|
self.fits_section = f'[{s.start}:{s.stop}]'
|
|
97
107
|
|
|
@@ -136,6 +146,10 @@ class SliceRegion1D:
|
|
|
136
146
|
result = True
|
|
137
147
|
return result
|
|
138
148
|
|
|
149
|
+
def length(self):
|
|
150
|
+
"""Return the length of the slice."""
|
|
151
|
+
return self.python.stop - self.python.start
|
|
152
|
+
|
|
139
153
|
|
|
140
154
|
class SliceRegion2D:
|
|
141
155
|
"""Store indices for slicing of 2D regions.
|
|
@@ -162,7 +176,7 @@ class SliceRegion2D:
|
|
|
162
176
|
within(other)
|
|
163
177
|
Check if slice 'other' is within the parent slice."""
|
|
164
178
|
|
|
165
|
-
def __init__(self, region, mode=None):
|
|
179
|
+
def __init__(self, region, mode=None, naxis1=None, naxis2=None):
|
|
166
180
|
"""Initialize SliceRegion2D.
|
|
167
181
|
|
|
168
182
|
Parameters
|
|
@@ -174,6 +188,14 @@ class SliceRegion2D:
|
|
|
174
188
|
mode : str
|
|
175
189
|
Convention mode employed to define the slice.
|
|
176
190
|
The two possible modes are 'fits' and 'python'.
|
|
191
|
+
naxis1 : int
|
|
192
|
+
The axis 1 size (length) of the data being sliced,
|
|
193
|
+
assuming the FITS convention.
|
|
194
|
+
If provided, it is used to validate the slice region.
|
|
195
|
+
naxis2 : int
|
|
196
|
+
The axis 2 size (length) of the data being sliced,
|
|
197
|
+
assuming the FITS convention.
|
|
198
|
+
If provided, it is used to validate the slice region.
|
|
177
199
|
"""
|
|
178
200
|
if isinstance(region, str):
|
|
179
201
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -223,6 +245,17 @@ class SliceRegion2D:
|
|
|
223
245
|
s1, s2 = self.fits
|
|
224
246
|
self.fits_section = f'[{s1.start}:{s1.stop},{s2.start}:{s2.stop}]'
|
|
225
247
|
|
|
248
|
+
if naxis1 is not None:
|
|
249
|
+
if not (1 <= s1.start <= naxis1):
|
|
250
|
+
raise ValueError(f'Invalid start={s1.start} for naxis1={naxis1}')
|
|
251
|
+
if not (1 <= s1.stop <= naxis1):
|
|
252
|
+
raise ValueError(f'Invalid stop={s1.stop} for naxis1={naxis1}')
|
|
253
|
+
if naxis2 is not None:
|
|
254
|
+
if not (1 <= s2.start <= naxis2):
|
|
255
|
+
raise ValueError(f'Invalid start={s2.start} for naxis2={naxis2}')
|
|
256
|
+
if not (1 <= s2.stop <= naxis2):
|
|
257
|
+
raise ValueError(f'Invalid stop={s2.stop} for naxis2={naxis2}')
|
|
258
|
+
|
|
226
259
|
def __eq__(self, other):
|
|
227
260
|
return self.fits == other.fits and self.python == other.python
|
|
228
261
|
|
|
@@ -268,6 +301,11 @@ class SliceRegion2D:
|
|
|
268
301
|
result = True
|
|
269
302
|
return result
|
|
270
303
|
|
|
304
|
+
def area(self):
|
|
305
|
+
"""Return the area of the slice."""
|
|
306
|
+
s1, s2 = self.python
|
|
307
|
+
return (s1.stop - s1.start) * (s2.stop - s2.start)
|
|
308
|
+
|
|
271
309
|
|
|
272
310
|
class SliceRegion3D:
|
|
273
311
|
"""Store indices for slicing of 3D regions.
|
|
@@ -294,7 +332,7 @@ class SliceRegion3D:
|
|
|
294
332
|
within(other)
|
|
295
333
|
Check if slice 'other' is within the parent slice."""
|
|
296
334
|
|
|
297
|
-
def __init__(self, region, mode=None):
|
|
335
|
+
def __init__(self, region, mode=None, naxis1=None, naxis2=None, naxis3=None):
|
|
298
336
|
"""Initialize SliceRegion3D.
|
|
299
337
|
|
|
300
338
|
Parameters
|
|
@@ -306,6 +344,18 @@ class SliceRegion3D:
|
|
|
306
344
|
mode : str
|
|
307
345
|
Convention mode employed to define the slice.
|
|
308
346
|
The two possible modes are 'fits' and 'python'.
|
|
347
|
+
naxis1 : int
|
|
348
|
+
The axis 1 size (length) of the data being sliced,
|
|
349
|
+
assuming the FITS convention.
|
|
350
|
+
If provided, it is used to validate the slice region.
|
|
351
|
+
naxis2 : int
|
|
352
|
+
The axis 2 size (length) of the data being sliced,
|
|
353
|
+
assuming the FITS convention.
|
|
354
|
+
If provided, it is used to validate the slice region.
|
|
355
|
+
naxis3 : int
|
|
356
|
+
The axis 3 size (length) of the data being sliced,
|
|
357
|
+
assuming the FITS convention.
|
|
358
|
+
If provided, it is used to validate the slice region.
|
|
309
359
|
"""
|
|
310
360
|
if isinstance(region, str):
|
|
311
361
|
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
@@ -359,6 +409,22 @@ class SliceRegion3D:
|
|
|
359
409
|
s1, s2, s3 = self.fits
|
|
360
410
|
self.fits_section = f'[{s1.start}:{s1.stop},{s2.start}:{s2.stop},{s3.start}:{s3.stop}]'
|
|
361
411
|
|
|
412
|
+
if naxis1 is not None:
|
|
413
|
+
if not (1 <= s1.start <= naxis1):
|
|
414
|
+
raise ValueError(f'Invalid start={s1.start} for naxis1={naxis1}')
|
|
415
|
+
if not (1 <= s1.stop <= naxis1):
|
|
416
|
+
raise ValueError(f'Invalid stop={s1.stop} for naxis1={naxis1}')
|
|
417
|
+
if naxis2 is not None:
|
|
418
|
+
if not (1 <= s2.start <= naxis2):
|
|
419
|
+
raise ValueError(f'Invalid start={s2.start} for naxis2={naxis2}')
|
|
420
|
+
if not (1 <= s2.stop <= naxis2):
|
|
421
|
+
raise ValueError(f'Invalid stop={s2.stop} for naxis2={naxis2}')
|
|
422
|
+
if naxis3 is not None:
|
|
423
|
+
if not (1 <= s3.start <= naxis3):
|
|
424
|
+
raise ValueError(f'Invalid start={s3.start} for naxis3={naxis3}')
|
|
425
|
+
if not (1 <= s3.stop <= naxis3):
|
|
426
|
+
raise ValueError(f'Invalid stop={s3.stop} for naxis3={naxis3}')
|
|
427
|
+
|
|
362
428
|
def __eq__(self, other):
|
|
363
429
|
return self.fits == other.fits and self.python == other.python
|
|
364
430
|
|
|
@@ -407,3 +473,8 @@ class SliceRegion3D:
|
|
|
407
473
|
return result
|
|
408
474
|
result = True
|
|
409
475
|
return result
|
|
476
|
+
|
|
477
|
+
def volume(self):
|
|
478
|
+
"""Return the volume of the slice."""
|
|
479
|
+
s1, s2, s3 = self.python
|
|
480
|
+
return (s1.stop - s1.start) * (s2.stop - s2.start) * (s3.stop - s3.start)
|
teareduce/statsummary.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2022-
|
|
2
|
+
# Copyright 2022-2025 Universidad Complutense de Madrid
|
|
3
3
|
#
|
|
4
4
|
# This file is part of teareduce
|
|
5
5
|
#
|
|
@@ -22,7 +22,7 @@ def statsummary(x=None, rm_nan=False, show=True):
|
|
|
22
22
|
Parameters
|
|
23
23
|
----------
|
|
24
24
|
x : numpy array or None
|
|
25
|
-
Input array with values which statistical properties are
|
|
25
|
+
Input array with values which statistical properties are
|
|
26
26
|
requested.
|
|
27
27
|
rm_nan : bool
|
|
28
28
|
If True, filter out NaN values before computing statistics.
|
|
@@ -42,9 +42,9 @@ def statsummary(x=None, rm_nan=False, show=True):
|
|
|
42
42
|
|
|
43
43
|
# protections
|
|
44
44
|
if x is None:
|
|
45
|
-
return ['npoints', 'minimum', 'maximum',
|
|
46
|
-
'mean', 'median', 'std', 'robust_std',
|
|
47
|
-
'percentile16', 'percentile25', 'percentile75', 'percentile84']
|
|
45
|
+
return ['npoints', 'minimum', 'maximum',
|
|
46
|
+
'mean', 'median', 'std', 'robust_std',
|
|
47
|
+
'percentile16', 'percentile25', 'percentile75', 'percentile84']
|
|
48
48
|
|
|
49
49
|
if isinstance(x, np.ndarray):
|
|
50
50
|
xx = np.copy(x.flatten())
|
|
@@ -32,6 +32,7 @@ def test_slice_region_creation():
|
|
|
32
32
|
assert region3d.python == (slice(1, 10, None), slice(2, 20, None), slice(3, 30, None))
|
|
33
33
|
assert region3d.fits_section == '[4:30,3:20,2:10]'
|
|
34
34
|
|
|
35
|
+
|
|
35
36
|
def test_slice_values():
|
|
36
37
|
"""Test the values of the slices in different modes."""
|
|
37
38
|
|
|
@@ -47,3 +48,69 @@ def test_slice_values():
|
|
|
47
48
|
array3d = np.arange(24).reshape(3, 4, 2)
|
|
48
49
|
region3d = SliceRegion3D(np.s_[1:3, 2:4, 1:2], mode='python')
|
|
49
50
|
assert np.all(array3d[region3d.python] == np.array([[[13], [15]], [[21], [23]]]))
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def test_wrong_number_of_dimensions():
|
|
54
|
+
"""Test the creation of a SliceRegion with wrong slices."""
|
|
55
|
+
|
|
56
|
+
try:
|
|
57
|
+
SliceRegion1D(np.s_[1:3, 2:4], mode='python')
|
|
58
|
+
assert False, "Expected ValueError for 1D slice with 2D input"
|
|
59
|
+
except ValueError:
|
|
60
|
+
pass
|
|
61
|
+
|
|
62
|
+
try:
|
|
63
|
+
SliceRegion2D(np.s_[1:3], mode='python')
|
|
64
|
+
assert False, "Expected ValueError for 2D slice with 1D input"
|
|
65
|
+
except ValueError:
|
|
66
|
+
pass
|
|
67
|
+
|
|
68
|
+
try:
|
|
69
|
+
SliceRegion3D(np.s_[1:3, 2:4], mode='python')
|
|
70
|
+
assert False, "Expected ValueError for 3D slice with 2D input"
|
|
71
|
+
except ValueError:
|
|
72
|
+
pass
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_wrong_limits_order():
|
|
76
|
+
"""Test the creation of a SliceRegion with wrong limits."""
|
|
77
|
+
|
|
78
|
+
try:
|
|
79
|
+
SliceRegion1D(np.s_[10:5], mode='python')
|
|
80
|
+
assert False, "Expected ValueError for 1D slice with start > stop"
|
|
81
|
+
except ValueError:
|
|
82
|
+
pass
|
|
83
|
+
|
|
84
|
+
try:
|
|
85
|
+
SliceRegion2D(np.s_[1:3, 5:2], mode='python')
|
|
86
|
+
assert False, "Expected ValueError for 2D slice with start > stop in second dimension"
|
|
87
|
+
except ValueError:
|
|
88
|
+
pass
|
|
89
|
+
|
|
90
|
+
try:
|
|
91
|
+
SliceRegion3D(np.s_[1:3, 2:4, 6:1], mode='python')
|
|
92
|
+
assert False, "Expected ValueError for 3D slice with start > stop in third dimension"
|
|
93
|
+
except ValueError:
|
|
94
|
+
pass
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
def test_limits_out_of_range():
|
|
98
|
+
"""Test the creation of a SliceRegion with limits out of range."""
|
|
99
|
+
|
|
100
|
+
try:
|
|
101
|
+
SliceRegion1D(np.s_[-1:5], mode='python', naxis1=10)
|
|
102
|
+
assert False, "Expected ValueError for 1D slice with negative start"
|
|
103
|
+
except ValueError:
|
|
104
|
+
pass
|
|
105
|
+
|
|
106
|
+
try:
|
|
107
|
+
SliceRegion2D(np.s_[1:3, 2:25], mode='python', naxis1=10, naxis2=20)
|
|
108
|
+
assert False, "Expected ValueError for 2D slice with stop > naxis in second dimension"
|
|
109
|
+
except ValueError:
|
|
110
|
+
pass
|
|
111
|
+
|
|
112
|
+
try:
|
|
113
|
+
SliceRegion3D(np.s_[1:3, 2:4, 3:35], mode='python', naxis1=10, naxis2=20, naxis3=30)
|
|
114
|
+
assert False, "Expected ValueError for 3D slice with stop > naxis in third dimension"
|
|
115
|
+
except ValueError:
|
|
116
|
+
pass
|
teareduce/version.py
CHANGED
teareduce/wavecal.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# -*- coding: utf-8 -*-
|
|
2
2
|
#
|
|
3
|
-
# Copyright 2015-
|
|
3
|
+
# Copyright 2015-2025 Universidad Complutense de Madrid
|
|
4
4
|
#
|
|
5
5
|
# This file is part of teareduce
|
|
6
6
|
#
|
|
@@ -30,10 +30,10 @@ from .sliceregion import SliceRegion1D
|
|
|
30
30
|
|
|
31
31
|
class TeaWaveCalibration:
|
|
32
32
|
"""Auxiliary class to compute and apply wavelength calibration.
|
|
33
|
-
|
|
33
|
+
|
|
34
34
|
It is assumed that the wavelength and spatial directions correspond
|
|
35
35
|
to the X (array columns) and Y (array rows) axes, respectively.
|
|
36
|
-
|
|
36
|
+
|
|
37
37
|
Attributes
|
|
38
38
|
----------
|
|
39
39
|
ns_window : int
|
|
@@ -44,7 +44,7 @@ class TeaWaveCalibration:
|
|
|
44
44
|
Standard deviation for Gaussian kernel to smooth median spectrum.
|
|
45
45
|
A value of 0 means that no smoothing is performed.
|
|
46
46
|
nx_window : int
|
|
47
|
-
Number of pixels (spectral direction) of the window where the
|
|
47
|
+
Number of pixels (spectral direction) of the window where the
|
|
48
48
|
peaks are sought. It must be odd.
|
|
49
49
|
delta_flux : float
|
|
50
50
|
Minimum difference between the flux at the line center and the
|
|
@@ -293,7 +293,7 @@ class TeaWaveCalibration:
|
|
|
293
293
|
Standard deviation for Gaussian kernel to smooth median spectrum.
|
|
294
294
|
A value of 0 means that no smoothing is performed.
|
|
295
295
|
nx_window : int or None
|
|
296
|
-
Number of pixels (spectral direction) of the window where the
|
|
296
|
+
Number of pixels (spectral direction) of the window where the
|
|
297
297
|
peaks are sought. It must be odd.
|
|
298
298
|
delta_flux : float
|
|
299
299
|
Minimum difference between the flux at the line center and the
|
|
@@ -335,7 +335,7 @@ class TeaWaveCalibration:
|
|
|
335
335
|
self.delta_flux = delta_flux
|
|
336
336
|
if method is not None:
|
|
337
337
|
self.method = method
|
|
338
|
-
|
|
338
|
+
|
|
339
339
|
naxis2, naxis1 = data.shape
|
|
340
340
|
|
|
341
341
|
if self._naxis1 is None:
|
|
@@ -343,7 +343,7 @@ class TeaWaveCalibration:
|
|
|
343
343
|
else:
|
|
344
344
|
if naxis1 != self._naxis1:
|
|
345
345
|
raise ValueError(f'Unexpected naxis1: {naxis1}')
|
|
346
|
-
|
|
346
|
+
|
|
347
347
|
if self._naxis2 is None:
|
|
348
348
|
self._naxis2 = naxis2
|
|
349
349
|
else:
|
|
@@ -363,7 +363,7 @@ class TeaWaveCalibration:
|
|
|
363
363
|
|
|
364
364
|
# initial median spectrum
|
|
365
365
|
xpeaks, ixpeaks, sp_median_smooth = self._find_peaks_scan(
|
|
366
|
-
data=data,
|
|
366
|
+
data=data,
|
|
367
367
|
ns1=ns1,
|
|
368
368
|
ns2=ns2,
|
|
369
369
|
plot_peaks=plot_peaks,
|
|
@@ -420,12 +420,12 @@ class TeaWaveCalibration:
|
|
|
420
420
|
xpeaks_reference=None,
|
|
421
421
|
ns_range=None,
|
|
422
422
|
direction='up',
|
|
423
|
-
ns_window
|
|
424
|
-
threshold
|
|
425
|
-
sigma_smooth
|
|
426
|
-
nx_window
|
|
427
|
-
delta_flux
|
|
428
|
-
method
|
|
423
|
+
ns_window=None,
|
|
424
|
+
threshold=None,
|
|
425
|
+
sigma_smooth=None,
|
|
426
|
+
nx_window=None,
|
|
427
|
+
delta_flux=None,
|
|
428
|
+
method=None,
|
|
429
429
|
plots=False,
|
|
430
430
|
title=None,
|
|
431
431
|
pdf_output=None,
|
|
@@ -455,7 +455,7 @@ class TeaWaveCalibration:
|
|
|
455
455
|
Standard deviation for Gaussian kernel to smooth median spectrum.
|
|
456
456
|
A value of 0 means that no smoothing is performed.
|
|
457
457
|
nx_window : int or None
|
|
458
|
-
Number of pixels (spectral direction) of the window where the
|
|
458
|
+
Number of pixels (spectral direction) of the window where the
|
|
459
459
|
peaks are sought. It must be odd.
|
|
460
460
|
delta_flux : float
|
|
461
461
|
Minimum difference between the flux at the line center and the
|
|
@@ -540,7 +540,7 @@ class TeaWaveCalibration:
|
|
|
540
540
|
color_previous = ['blue', 'cyan']
|
|
541
541
|
fig, ax = plt.subplots(figsize=(15, 15*naxis2/naxis1))
|
|
542
542
|
vmin, vmax = np.percentile(data, [5, 95])
|
|
543
|
-
|
|
543
|
+
imshow(fig, ax, data, vmin=vmin, vmax=vmax, cmap='gray', title=title, aspect='auto')
|
|
544
544
|
# display previously identified lines
|
|
545
545
|
yplot = np.arange(naxis2)[self._valid_scans]
|
|
546
546
|
for i in range(self._nlines_reference):
|
|
@@ -549,18 +549,17 @@ class TeaWaveCalibration:
|
|
|
549
549
|
else:
|
|
550
550
|
fig = None
|
|
551
551
|
ax = None
|
|
552
|
-
img = None
|
|
553
552
|
|
|
554
553
|
# search for peaks in the 2D image
|
|
555
554
|
dict_xpeaks = dict()
|
|
556
|
-
for ns in tqdm(range(ns_min_fits, ns_max_fits + ns_step, ns_step),
|
|
555
|
+
for ns in tqdm(range(ns_min_fits, ns_max_fits + ns_step, ns_step),
|
|
557
556
|
desc='Finding peaks', disable=disable_tqdm):
|
|
558
557
|
ns1 = ns - self.ns_window // 2
|
|
559
558
|
ns1 = max([ns1, min(ns_min_fits, ns_max_fits)])
|
|
560
559
|
ns2 = ns + self.ns_window // 2
|
|
561
560
|
ns2 = min([ns2, max(ns_min_fits, ns_max_fits)])
|
|
562
561
|
xpeaks, ixpeaks, sp_median_smooth = self._find_peaks_scan(
|
|
563
|
-
data=data,
|
|
562
|
+
data=data,
|
|
564
563
|
ns1=ns1,
|
|
565
564
|
ns2=ns2,
|
|
566
565
|
plot_peaks=False,
|
|
@@ -585,7 +584,7 @@ class TeaWaveCalibration:
|
|
|
585
584
|
xpeaks_predicted = np.median(self._xpeaks_all_lines_array[(ns1-1):ns2, :], axis=0)
|
|
586
585
|
for i in range(self._nlines_reference):
|
|
587
586
|
value = xpeaks_predicted[i]
|
|
588
|
-
# if there is no peak near to the expected location
|
|
587
|
+
# if there is no peak near to the expected location
|
|
589
588
|
# (within a distance given by self.nx_window) the line
|
|
590
589
|
# is probably weak and we need to avoid jumping into
|
|
591
590
|
# another line
|
|
@@ -661,7 +660,7 @@ class TeaWaveCalibration:
|
|
|
661
660
|
self.peak_wavelengths = wavelengths
|
|
662
661
|
|
|
663
662
|
@u.quantity_input(xpeaks=u.pixel)
|
|
664
|
-
def overplot_identified_lines(self, xpeaks, spectrum,
|
|
663
|
+
def overplot_identified_lines(self, xpeaks, spectrum,
|
|
665
664
|
title=None, fontsize_title=16, fontsize_wave=10,
|
|
666
665
|
pdf_output=None, pdf_only=False):
|
|
667
666
|
"""Overplot identified lines
|
|
@@ -790,7 +789,7 @@ class TeaWaveCalibration:
|
|
|
790
789
|
xfit = np.arange(self._naxis2)[self._valid_scans]
|
|
791
790
|
if len(xfit) <= self.degree_cdistortion:
|
|
792
791
|
raise ValueError(f'Insufficient number of points to fit a polynomial of degree {self.degree_cdistortion}')
|
|
793
|
-
for i in tqdm(range(self._nlines_reference),
|
|
792
|
+
for i in tqdm(range(self._nlines_reference),
|
|
794
793
|
desc='Fitting C distortion', disable=disable_tqdm):
|
|
795
794
|
yfit = self._xpeaks_all_lines_array[self._valid_scans, i]
|
|
796
795
|
poly, yres, reject = polfit_residuals_with_sigma_rejection(
|
|
@@ -1124,11 +1123,11 @@ class TeaWaveCalibration:
|
|
|
1124
1123
|
raise ValueError('You must set plots=True to make use of pdf_output')
|
|
1125
1124
|
|
|
1126
1125
|
return poly_fits_yx, residual_std_yx, \
|
|
1127
|
-
|
|
1128
|
-
|
|
1126
|
+
poly_fits_xy, residual_std_xy, \
|
|
1127
|
+
crval1_linear, cdelt1_linear, crmax1_linear
|
|
1129
1128
|
|
|
1130
1129
|
def fit_wavelengths(self, degree_wavecalib=None,
|
|
1131
|
-
output_filename=None, history_list=None,
|
|
1130
|
+
output_filename=None, history_list=None,
|
|
1132
1131
|
plots=False, title=None,
|
|
1133
1132
|
pdf_output=None, pdf_only=False,
|
|
1134
1133
|
silent_mode=False, disable_tqdm=True):
|
|
@@ -1320,9 +1319,9 @@ class TeaWaveCalibration:
|
|
|
1320
1319
|
# crval1, cdelt1, crmax1, residual_std
|
|
1321
1320
|
fig, axarr = plt.subplots(nrows=1, ncols=5, figsize=(15, 3))
|
|
1322
1321
|
axarr = axarr.flatten()
|
|
1323
|
-
for i, item in enumerate(['_array_crval1_linear',
|
|
1322
|
+
for i, item in enumerate(['_array_crval1_linear',
|
|
1324
1323
|
'_array_cdelt1_linear',
|
|
1325
|
-
'_array_crmax1_linear',
|
|
1324
|
+
'_array_crmax1_linear',
|
|
1326
1325
|
'_array_residual_std_wav',
|
|
1327
1326
|
'_array_residual_std_pix']):
|
|
1328
1327
|
ax = axarr[i]
|
|
@@ -1357,8 +1356,7 @@ class TeaWaveCalibration:
|
|
|
1357
1356
|
nrows = int(ncoeff / npprow)
|
|
1358
1357
|
if ncoeff % npprow != 0:
|
|
1359
1358
|
nrows += 1
|
|
1360
|
-
fig, axarr = plt.subplots(nrows=nrows, ncols=npprow,
|
|
1361
|
-
figsize=(figwidth, 3*nrows))
|
|
1359
|
+
fig, axarr = plt.subplots(nrows=nrows, ncols=npprow, figsize=(figwidth, 3*nrows))
|
|
1362
1360
|
axarr = axarr.flatten()
|
|
1363
1361
|
for ax in axarr:
|
|
1364
1362
|
ax.axis('off')
|
|
@@ -1435,7 +1433,7 @@ class TeaWaveCalibration:
|
|
|
1435
1433
|
|
|
1436
1434
|
old_x_borders_fits = np.arange(naxis1 + 1) + 0.5 # FITS convention
|
|
1437
1435
|
|
|
1438
|
-
for k in tqdm(range(naxis2),
|
|
1436
|
+
for k in tqdm(range(naxis2),
|
|
1439
1437
|
desc='Applying wavelength calibration',
|
|
1440
1438
|
disable=disable_tqdm):
|
|
1441
1439
|
poly = Polynomial(self._array_poly_wav[k])
|
|
@@ -1678,4 +1676,3 @@ def apply_wavecal_ccddata(infile, wcalibfile, outfile,
|
|
|
1678
1676
|
cdelt1=cdelt1,
|
|
1679
1677
|
title=f'{title_}(UNCERT extension)'
|
|
1680
1678
|
)
|
|
1681
|
-
|
teareduce/zscale.py
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
#
|
|
2
|
-
# Copyright 2022-
|
|
2
|
+
# Copyright 2022-2025 Universidad Complutense de Madrid
|
|
3
3
|
#
|
|
4
4
|
# This file is part of teareduce
|
|
5
5
|
#
|
|
@@ -12,8 +12,8 @@ import numpy as np
|
|
|
12
12
|
|
|
13
13
|
def zscale(image, factor=0.25):
|
|
14
14
|
"""Compute z1 and z2 cuts in a similar way to Iraf.
|
|
15
|
-
|
|
16
|
-
If the total number of pixels is less than 10, the function simply
|
|
15
|
+
|
|
16
|
+
If the total number of pixels is less than 10, the function simply
|
|
17
17
|
returns the minimum and the maximum values.
|
|
18
18
|
|
|
19
19
|
Parameters
|
|
@@ -48,5 +48,5 @@ def zscale(image, factor=0.25):
|
|
|
48
48
|
z1 = max(z1, q000)
|
|
49
49
|
z2 = q500+(zslope*npixels/2)/factor
|
|
50
50
|
z2 = min(z2, q1000)
|
|
51
|
-
|
|
51
|
+
|
|
52
52
|
return z1, z2
|