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/ctext.py CHANGED
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright 2022-2024 Universidad Complutense de Madrid
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
-
@@ -1,5 +1,5 @@
1
1
  #
2
- # Copyright 2022-2024 Universidad Complutense de Madrid
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 'extent' in kwargs:
133
- extent = kwargs['extent']
134
- del kwargs['extent']
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
- extent = None
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']
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright 2015-2024 Universidad Complutense de Madrid
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-2024 Universidad Complutense de Madrid
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
@@ -9,7 +9,7 @@
9
9
  #
10
10
  """Module to define the version of the teareduce package."""
11
11
 
12
- VERSION = '0.4.5'
12
+ VERSION = '0.4.7'
13
13
 
14
14
 
15
15
  def main():
teareduce/wavecal.py CHANGED
@@ -1,6 +1,6 @@
1
1
  # -*- coding: utf-8 -*-
2
2
  #
3
- # Copyright 2015-2024 Universidad Complutense de Madrid
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 = None,
424
- threshold = None,
425
- sigma_smooth = None,
426
- nx_window = None,
427
- delta_flux = None,
428
- method = None,
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
- img = imshow(fig, ax, data, vmin=vmin, vmax=vmax, cmap='gray', title=title, aspect='auto')
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
- poly_fits_xy, residual_std_xy, \
1128
- crval1_linear, cdelt1_linear, crmax1_linear
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-2024 Universidad Complutense de Madrid
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
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: teareduce
3
- Version: 0.4.5
3
+ Version: 0.4.7
4
4
  Summary: Utilities for astronomical data reduction
5
5
  Author-email: Nicolás Cardiel <cardiel@ucm.es>
6
6
  License: GPL-3.0-or-later