teareduce 0.4.1__tar.gz → 0.4.3__tar.gz
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-0.4.1/src/teareduce.egg-info → teareduce-0.4.3}/PKG-INFO +7 -8
- {teareduce-0.4.1 → teareduce-0.4.3}/pyproject.toml +6 -8
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/__init__.py +6 -4
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/cosmicrays.py +78 -27
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/elapsed_time.py +3 -3
- teareduce-0.4.3/src/teareduce/histogram1d.py +85 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/simulateccdexposure.py +6 -6
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/sliceregion.py +161 -15
- teareduce-0.4.3/src/teareduce/tests/__init__.py +0 -0
- teareduce-0.4.3/src/teareduce/tests/test_sliceregion.py +49 -0
- teareduce-0.4.3/src/teareduce/version.py +21 -0
- {teareduce-0.4.1 → teareduce-0.4.3/src/teareduce.egg-info}/PKG-INFO +7 -8
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce.egg-info/SOURCES.txt +4 -2
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce.egg-info/requires.txt +1 -2
- teareduce-0.4.1/src/teareduce/version.py +0 -19
- {teareduce-0.4.1 → teareduce-0.4.3}/LICENSE.txt +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/README.md +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/setup.cfg +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/avoid_astropy_warnings.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/correct_pincushion_distortion.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/ctext.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/draw_rectangle.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/imshow.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/numsplines.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/peaks_spectrum.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/polfit.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/robust_std.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/sdistortion.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/statsummary.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/wavecal.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/write_array_to_fits.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce/zscale.py +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce.egg-info/dependency_links.txt +0 -0
- {teareduce-0.4.1 → teareduce-0.4.3}/src/teareduce.egg-info/top_level.txt +0 -0
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: teareduce
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://github.com/nicocardiel/teareduce
|
|
8
8
|
Project-URL: Repository, https://github.com/nicocardiel/teareduce.git
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
13
|
Classifier: Development Status :: 3 - Alpha
|
|
15
14
|
Classifier: Environment :: Console
|
|
16
15
|
Classifier: Intended Audience :: Science/Research
|
|
17
|
-
Classifier: License :: OSI Approved :: GNU General Public License (
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
18
17
|
Classifier: Operating System :: OS Independent
|
|
19
18
|
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
20
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.10
|
|
21
20
|
Description-Content-Type: text/markdown
|
|
22
21
|
License-File: LICENSE.txt
|
|
23
22
|
Requires-Dist: astropy
|
|
24
|
-
Requires-Dist: importlib_resources
|
|
25
23
|
Requires-Dist: lmfit
|
|
26
24
|
Requires-Dist: matplotlib
|
|
27
|
-
Requires-Dist: numpy>=1.
|
|
25
|
+
Requires-Dist: numpy>=1.22
|
|
28
26
|
Requires-Dist: scipy
|
|
29
27
|
Requires-Dist: tqdm
|
|
30
28
|
Provides-Extra: test
|
|
31
29
|
Requires-Dist: pytest; extra == "test"
|
|
30
|
+
Dynamic: license-file
|
|
32
31
|
|
|
33
32
|
# teareduce
|
|
34
33
|
Utilities for astronomical data reduction
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
# Minimum requirements for the build system to execute.
|
|
3
3
|
# (include packages imported in the different modules; otherwise
|
|
4
4
|
# a ModuleNotFoundError is raised when using $ pip install -e .)
|
|
5
|
-
requires = ["setuptools >=
|
|
6
|
-
"astropy", "matplotlib", "scipy", "lmfit", "tqdm"]
|
|
5
|
+
requires = ["setuptools >= 61.0.0", "wheel",
|
|
6
|
+
"astropy", "matplotlib", "numpy>=1.22","scipy", "lmfit", "tqdm"]
|
|
7
7
|
build-backend = "setuptools.build_meta"
|
|
8
8
|
|
|
9
9
|
[project]
|
|
@@ -11,31 +11,29 @@ name = "teareduce"
|
|
|
11
11
|
dynamic = ["version"]
|
|
12
12
|
description = "Utilities for astronomical data reduction"
|
|
13
13
|
readme = "README.md"
|
|
14
|
-
requires-python = ">=3.
|
|
14
|
+
requires-python = ">=3.10"
|
|
15
15
|
license = {text = "GPL-3.0-or-later"}
|
|
16
16
|
authors = [
|
|
17
17
|
{name = "Nicolás Cardiel", email = "cardiel@ucm.es"},
|
|
18
18
|
]
|
|
19
19
|
classifiers = [
|
|
20
|
-
"Programming Language :: Python :: 3.8",
|
|
21
|
-
"Programming Language :: Python :: 3.9",
|
|
22
20
|
"Programming Language :: Python :: 3.10",
|
|
23
21
|
"Programming Language :: Python :: 3.11",
|
|
24
22
|
"Programming Language :: Python :: 3.12",
|
|
23
|
+
"Programming Language :: Python :: 3.13",
|
|
25
24
|
"Development Status :: 3 - Alpha",
|
|
26
25
|
"Environment :: Console",
|
|
27
26
|
"Intended Audience :: Science/Research",
|
|
28
|
-
"License :: OSI Approved :: GNU General Public License (
|
|
27
|
+
"License :: OSI Approved :: GNU General Public License v3 (GPLv3)",
|
|
29
28
|
"Operating System :: OS Independent",
|
|
30
29
|
"Topic :: Scientific/Engineering :: Astronomy",
|
|
31
30
|
]
|
|
32
31
|
|
|
33
32
|
dependencies = [
|
|
34
33
|
"astropy",
|
|
35
|
-
"importlib_resources", # required with python < 3.9
|
|
36
34
|
"lmfit",
|
|
37
35
|
"matplotlib",
|
|
38
|
-
"numpy >= 1.
|
|
36
|
+
"numpy >= 1.22",
|
|
39
37
|
"scipy",
|
|
40
38
|
"tqdm",
|
|
41
39
|
]
|
|
@@ -14,6 +14,8 @@ from .ctext import ctext
|
|
|
14
14
|
from .draw_rectangle import draw_rectangle
|
|
15
15
|
from .elapsed_time import elapsed_time
|
|
16
16
|
from .elapsed_time import elapsed_time_since
|
|
17
|
+
from .histogram1d import hist_step
|
|
18
|
+
from .histogram1d import plot_hist_step
|
|
17
19
|
from .imshow import imshow
|
|
18
20
|
from .imshow import imshowme
|
|
19
21
|
from .numsplines import AdaptiveLSQUnivariateSpline
|
|
@@ -21,12 +23,12 @@ from .peaks_spectrum import find_peaks_spectrum, refine_peaks_spectrum
|
|
|
21
23
|
from .polfit import polfit_residuals, polfit_residuals_with_sigma_rejection
|
|
22
24
|
from .robust_std import robust_std
|
|
23
25
|
from .sdistortion import fit_sdistortion
|
|
24
|
-
from .sliceregion import SliceRegion1D, SliceRegion2D
|
|
25
|
-
from .statsummary import ifc_statsummary, statsummary
|
|
26
26
|
from .simulateccdexposure import SimulateCCDExposure
|
|
27
|
-
from .
|
|
27
|
+
from .sliceregion import SliceRegion1D, SliceRegion2D, SliceRegion3D
|
|
28
|
+
from .statsummary import ifc_statsummary, statsummary
|
|
29
|
+
from .version import VERSION
|
|
28
30
|
from .wavecal import TeaWaveCalibration, apply_wavecal_ccddata
|
|
29
31
|
from .write_array_to_fits import write_array_to_fits
|
|
30
32
|
from .zscale import zscale
|
|
31
33
|
|
|
32
|
-
__version__ =
|
|
34
|
+
__version__ = VERSION
|
|
@@ -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
|
#
|
|
@@ -20,14 +20,17 @@ from .robust_std import robust_std
|
|
|
20
20
|
from .sliceregion import SliceRegion2D
|
|
21
21
|
|
|
22
22
|
|
|
23
|
-
def cr2images(data1, data2=None,
|
|
23
|
+
def cr2images(data1, data2=None,
|
|
24
|
+
single_mode=False,
|
|
25
|
+
ioffx=0, ioffy=0,
|
|
24
26
|
tsigma_peak=10, tsigma_tail=3, maxsize=None,
|
|
25
27
|
list_skipped_regions=None,
|
|
26
28
|
image_region=None,
|
|
27
29
|
median_size=None,
|
|
28
30
|
return_masks=False,
|
|
29
31
|
debug_level=0,
|
|
30
|
-
zoom_region_imshow=None
|
|
32
|
+
zoom_region_imshow=None,
|
|
33
|
+
aspect='equal'):
|
|
31
34
|
"""Remove cosmic rays from differences between 2 images.
|
|
32
35
|
|
|
33
36
|
The input images must have the same shape. If only 1 image
|
|
@@ -42,6 +45,10 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
42
45
|
Second image. If None, a median filtered version of 'data1'
|
|
43
46
|
is employed. In this case, the parameter 'median_size' must
|
|
44
47
|
be properly set.
|
|
48
|
+
single_mode : bool
|
|
49
|
+
If True, the function is used in single mode, i.e., only the
|
|
50
|
+
first image is cleaned (default=False). When the second image
|
|
51
|
+
is None, this parameter is automatically set to True.
|
|
45
52
|
ioffx : int
|
|
46
53
|
Integer offset (pixels) to place the second image on top of
|
|
47
54
|
the first image in the horizontal direction (axis=1) in the
|
|
@@ -55,9 +62,13 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
55
62
|
tsigma_tail : float
|
|
56
63
|
Times sigma to detect additional pixels affected by cosmic
|
|
57
64
|
rays.
|
|
58
|
-
maxsize : int
|
|
59
|
-
|
|
60
|
-
|
|
65
|
+
maxsize : int or None
|
|
66
|
+
If not None, this parameter sets the maximum number of pixels
|
|
67
|
+
affected by cosmic rays in a single region. If the number of
|
|
68
|
+
pixels affected by a single cosmic ray is larger
|
|
69
|
+
than this value, the region is not cleaned. If None, all
|
|
70
|
+
regions are cleaned regardless of the number of pixels
|
|
71
|
+
affected by single cosmic rays.
|
|
61
72
|
list_skipped_regions : list or None
|
|
62
73
|
List of SliceRegion2D instances indicating image regions where
|
|
63
74
|
detected cosmic rays will not be removed. The indices refer
|
|
@@ -80,6 +91,8 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
80
91
|
zoom_region_imshow : SliceRegion2D instance or None
|
|
81
92
|
If not None, display intermediate images, zooming in the
|
|
82
93
|
indicated region.
|
|
94
|
+
aspect : str
|
|
95
|
+
Aspect ratio of the displayed images. Default is 'equal'.
|
|
83
96
|
|
|
84
97
|
Returns
|
|
85
98
|
-------
|
|
@@ -103,11 +116,9 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
103
116
|
if (ioffx != 0) or (ioffy != 0):
|
|
104
117
|
raise ValueError(f'ERROR: ioffx={ioffx} and ioffy={ioffy} must be zero!')
|
|
105
118
|
if median_size is None:
|
|
106
|
-
raise ValueError(
|
|
119
|
+
raise ValueError('ERROR: you must specify median_size when only one image is available')
|
|
107
120
|
data2 = ndimage.median_filter(data1, size=median_size)
|
|
108
121
|
single_mode = True
|
|
109
|
-
else:
|
|
110
|
-
single_mode = False
|
|
111
122
|
|
|
112
123
|
if list_skipped_regions is not None and image_region is not None:
|
|
113
124
|
raise ValueError('list_skipped_regions and useful_region are incompatible')
|
|
@@ -183,9 +194,6 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
183
194
|
if shape1 != shape2:
|
|
184
195
|
raise ValueError('ERROR: overlapping regions have different shape')
|
|
185
196
|
|
|
186
|
-
if maxsize is None:
|
|
187
|
-
maxsize = shape1[0] * shape1[1]
|
|
188
|
-
|
|
189
197
|
# difference between the two overlapping regions
|
|
190
198
|
diff = subdata1 - subdata2
|
|
191
199
|
if debug_level > 0:
|
|
@@ -200,8 +208,20 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
200
208
|
print(f'>>> Robust_std: {std:.3f}')
|
|
201
209
|
|
|
202
210
|
# search for positive peaks (CR in data1)
|
|
211
|
+
if debug_level > 0:
|
|
212
|
+
print(f'>>> Searching for positive peaks (CR in data1) with tsigma_peak={tsigma_peak}')
|
|
203
213
|
labels_pos_peak, no_cr_pos_peak = ndimage.label(diff > median + tsigma_peak * std)
|
|
214
|
+
if debug_level > 0:
|
|
215
|
+
print(f'>>> Found {no_cr_pos_peak} positive peaks')
|
|
216
|
+
# search for additional pixels affected by cosmic rays (tail)
|
|
217
|
+
if debug_level > 0:
|
|
218
|
+
print(f'>>> Searching for positive tails (CR in data1) with tsigma_tail={tsigma_tail}')
|
|
204
219
|
labels_pos_tail, no_cr_pos_tail = ndimage.label(diff > median + tsigma_tail * std)
|
|
220
|
+
if debug_level > 0:
|
|
221
|
+
print(f'>>> Found {no_cr_pos_tail} positive tails')
|
|
222
|
+
# merge positive peaks and tails
|
|
223
|
+
if debug_level > 0:
|
|
224
|
+
print('>>> Merging positive peaks and tails')
|
|
205
225
|
# set all CR peak pixels to 1
|
|
206
226
|
mask_pos_peak = np.zeros_like(labels_pos_peak)
|
|
207
227
|
mask_pos_peak[labels_pos_peak > 0] = 1
|
|
@@ -211,13 +231,21 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
211
231
|
mask_pos_clean = np.zeros_like(labels_pos_peak)
|
|
212
232
|
for icr in np.unique(labels_pos_tail_in_peak):
|
|
213
233
|
if icr > 0:
|
|
214
|
-
|
|
215
|
-
if npix_affected <= maxsize:
|
|
234
|
+
if maxsize is None:
|
|
216
235
|
mask_pos_clean[labels_pos_tail == icr] = 1
|
|
236
|
+
else:
|
|
237
|
+
npix_affected = np.sum(labels_pos_tail == icr)
|
|
238
|
+
if npix_affected <= maxsize:
|
|
239
|
+
mask_pos_clean[labels_pos_tail == icr] = 1
|
|
240
|
+
|
|
217
241
|
# replace pixels affected by cosmic rays
|
|
242
|
+
if debug_level > 0:
|
|
243
|
+
print(f'>>> Replacing {np.sum(mask_pos_clean)} pixels affected by cosmic rays in data1')
|
|
218
244
|
data1c = data1.copy()
|
|
219
245
|
for item in np.argwhere(mask_pos_clean):
|
|
220
246
|
data1c[item[0] + i1, item[1] + j1] = data2[item[0] + ii1, item[1] + jj1] + median
|
|
247
|
+
if debug_level > 0:
|
|
248
|
+
print('>>> Finished replacing pixels affected by cosmic rays in data1')
|
|
221
249
|
|
|
222
250
|
if single_mode:
|
|
223
251
|
data2c = data2.copy()
|
|
@@ -229,8 +257,20 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
229
257
|
no_cr_neg_tail = 0
|
|
230
258
|
else:
|
|
231
259
|
# search for negative peaks (CR in data2)
|
|
260
|
+
if debug_level > 0:
|
|
261
|
+
print(f'>>> Searching for negative peaks (CR in data2) with tsigma_peak={tsigma_peak}')
|
|
232
262
|
labels_neg_peak, no_cr_neg_peak = ndimage.label(diff < median - tsigma_peak * std)
|
|
263
|
+
if debug_level > 0:
|
|
264
|
+
print(f'>>> Found {no_cr_neg_peak} negative peaks')
|
|
265
|
+
# search for additional pixels affected by cosmic rays (tail)
|
|
266
|
+
if debug_level > 0:
|
|
267
|
+
print(f'>>> Searching for negative tails (CR in data2) with tsigma_tail={tsigma_tail}')
|
|
233
268
|
labels_neg_tail, no_cr_neg_tail = ndimage.label(diff < median - tsigma_tail * std)
|
|
269
|
+
if debug_level > 0:
|
|
270
|
+
print(f'>>> Found {no_cr_neg_tail} negative tails')
|
|
271
|
+
# merge negative peaks and tails
|
|
272
|
+
if debug_level > 0:
|
|
273
|
+
print('>>> Merging negative peaks and tails')
|
|
234
274
|
# set all CR peak pixels to 1
|
|
235
275
|
mask_neg_peak = np.zeros_like(labels_neg_peak)
|
|
236
276
|
mask_neg_peak[labels_neg_peak > 0] = 1
|
|
@@ -240,13 +280,20 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
240
280
|
mask_neg_clean = np.zeros_like(labels_neg_peak)
|
|
241
281
|
for icr in np.unique(labels_neg_tail_in_peak):
|
|
242
282
|
if icr > 0:
|
|
243
|
-
|
|
244
|
-
if npix_affected <= maxsize:
|
|
283
|
+
if maxsize is None:
|
|
245
284
|
mask_neg_clean[labels_neg_tail == icr] = 1
|
|
285
|
+
else:
|
|
286
|
+
npix_affected = np.sum(labels_neg_tail == icr)
|
|
287
|
+
if npix_affected <= maxsize:
|
|
288
|
+
mask_neg_clean[labels_neg_tail == icr] = 1
|
|
246
289
|
# replace pixels affected by cosmic rays
|
|
290
|
+
if debug_level > 0:
|
|
291
|
+
print(f'>>> Replacing {np.sum(mask_neg_clean)} pixels affected by cosmic rays in data2')
|
|
247
292
|
data2c = data2.copy()
|
|
248
293
|
for item in np.argwhere(mask_neg_clean):
|
|
249
294
|
data2c[item[0] + ii1, item[1] + jj1] = data1[item[0] + i1, item[1] + j1] - median
|
|
295
|
+
if debug_level > 0:
|
|
296
|
+
print('>>> Finished replacing pixels affected by cosmic rays in data2')
|
|
250
297
|
|
|
251
298
|
# insert result in arrays with the original data shape
|
|
252
299
|
mask_data1c = np.zeros((naxis2, naxis1), dtype=int)
|
|
@@ -269,7 +316,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
269
316
|
hmin = max(min(diff.flatten()), median - 10*tsigma_peak*std)
|
|
270
317
|
hmax = min(max(diff.flatten()), median + 10*tsigma_peak*std)
|
|
271
318
|
bins = np.linspace(hmin, hmax, 100)
|
|
272
|
-
fig, ax = plt.subplots(ncols=1, nrows=1
|
|
319
|
+
fig, ax = plt.subplots(ncols=1, nrows=1) # figsize=(12, 6)
|
|
273
320
|
ax.hist(diff[zoom_region_imshow.python].flatten(), bins=bins)
|
|
274
321
|
ax.set_xlabel('ADU')
|
|
275
322
|
ax.set_ylabel('Number of pixels')
|
|
@@ -277,10 +324,10 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
277
324
|
ax.set_yscale('log')
|
|
278
325
|
plt.show()
|
|
279
326
|
# display diff
|
|
280
|
-
fig, ax = plt.subplots(ncols=1, nrows=1
|
|
327
|
+
fig, ax = plt.subplots(ncols=1, nrows=1) # figsize=(15, 15*naxis2/naxis1)
|
|
281
328
|
vmin = median - tsigma_peak * std
|
|
282
329
|
vmax = median + tsigma_peak * std
|
|
283
|
-
imshow(fig, ax, diff, vmin=vmin, vmax=vmax, cmap='seismic')
|
|
330
|
+
imshow(fig, ax, diff, vmin=vmin, vmax=vmax, cmap='seismic', aspect=aspect)
|
|
284
331
|
ax.set_xlim([zoom_region_imshow.python[1].start, zoom_region_imshow.python[1].stop])
|
|
285
332
|
ax.set_ylim([zoom_region_imshow.python[0].start, zoom_region_imshow.python[0].stop])
|
|
286
333
|
ax.set_title('diff: overlapping data1 - data2')
|
|
@@ -291,7 +338,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
291
338
|
data1, labels_pos_peak, labels_pos_tail, labels_pos_tail_in_peak, data1, data1c
|
|
292
339
|
]
|
|
293
340
|
title_list1 = [
|
|
294
|
-
'data1', 'labels_pos_peak', 'labels_pos_tail', 'labels_pos_tail_in_peak',
|
|
341
|
+
'data1', 'labels_pos_peak', 'labels_pos_tail', 'labels_pos_tail_in_peak',
|
|
295
342
|
'data1 with C.R.', 'data1c'
|
|
296
343
|
]
|
|
297
344
|
if debug_level == 1:
|
|
@@ -305,7 +352,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
305
352
|
data2, labels_neg_peak, labels_neg_tail, labels_neg_tail_in_peak, data2, data2c
|
|
306
353
|
]
|
|
307
354
|
title_list2 = [
|
|
308
|
-
'data2', 'labels_neg_peak', 'labels_neg_tail', 'labels_neg_tail_in_peak',
|
|
355
|
+
'data2', 'labels_neg_peak', 'labels_neg_tail', 'labels_neg_tail_in_peak',
|
|
309
356
|
'data2 with C.R.', 'data2c'
|
|
310
357
|
]
|
|
311
358
|
if debug_level == 1:
|
|
@@ -329,7 +376,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
329
376
|
for iplot, (image, title) in enumerate(zip(image_list, title_list)):
|
|
330
377
|
imgplot = image[zoom_region_imshow.python]
|
|
331
378
|
naxis2_, naxis1_ = imgplot.shape
|
|
332
|
-
fig, ax = plt.subplots(ncols=1, nrows=1
|
|
379
|
+
fig, ax = plt.subplots(ncols=1, nrows=1) # figsize=(15, 15*naxis2_/naxis1_)
|
|
333
380
|
median_ = np.median(imgplot)
|
|
334
381
|
std_ = robust_std(imgplot)
|
|
335
382
|
if std_ == 0:
|
|
@@ -340,7 +387,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
340
387
|
vmin = median_ - 2 * std_
|
|
341
388
|
vmax = median_ + 5 * std_
|
|
342
389
|
cmap = 'gray'
|
|
343
|
-
imshow(fig, ax, image, vmin=vmin, vmax=vmax, cmap=cmap)
|
|
390
|
+
imshow(fig, ax, image, vmin=vmin, vmax=vmax, cmap=cmap, aspect=aspect)
|
|
344
391
|
ax.set_xlim([zoom_region_imshow.python[1].start, zoom_region_imshow.python[1].stop - 1])
|
|
345
392
|
ax.set_ylim([zoom_region_imshow.python[0].start, zoom_region_imshow.python[0].stop - 1])
|
|
346
393
|
ax.set_title(title)
|
|
@@ -385,7 +432,7 @@ def cr2images(data1, data2=None, ioffx=0, ioffy=0,
|
|
|
385
432
|
def apply_cr2images_ccddata(infile1, infile2=None, outfile1=None, outfile2=None,
|
|
386
433
|
ioffx=0, ioffy=0, tsigma_peak=10, tsigma_tail=3,
|
|
387
434
|
list_skipped_regions=None, image_region=None,
|
|
388
|
-
median_size=None, debug_level=0, zoom_region_imshow=None):
|
|
435
|
+
median_size=None, debug_level=0, zoom_region_imshow=None, aspect='equal'):
|
|
389
436
|
"""Apply cr2images() to FITS files storing CCDData.
|
|
390
437
|
|
|
391
438
|
The FITS file must contain:
|
|
@@ -444,6 +491,8 @@ def apply_cr2images_ccddata(infile1, infile2=None, outfile1=None, outfile2=None,
|
|
|
444
491
|
zoom_region_imshow : SliceRegion2D instance or None
|
|
445
492
|
If not None, display intermediate images, zooming in the
|
|
446
493
|
indicated region.
|
|
494
|
+
aspect : str
|
|
495
|
+
Aspect ratio of the displayed images. Default is 'equal'.
|
|
447
496
|
|
|
448
497
|
"""
|
|
449
498
|
|
|
@@ -451,7 +500,7 @@ def apply_cr2images_ccddata(infile1, infile2=None, outfile1=None, outfile2=None,
|
|
|
451
500
|
if (ioffx != 0) or (ioffy != 0):
|
|
452
501
|
raise ValueError(f'ERROR: ioffx={ioffx} and ioffy={ioffy} must be zero!')
|
|
453
502
|
if median_size is None:
|
|
454
|
-
raise ValueError(
|
|
503
|
+
raise ValueError('ERROR: you must specify median_size when only one image is available')
|
|
455
504
|
|
|
456
505
|
history_list = ['using cr2images:']
|
|
457
506
|
|
|
@@ -472,7 +521,8 @@ def apply_cr2images_ccddata(infile1, infile2=None, outfile1=None, outfile2=None,
|
|
|
472
521
|
image_region=image_region,
|
|
473
522
|
return_masks=True,
|
|
474
523
|
debug_level=debug_level,
|
|
475
|
-
zoom_region_imshow=zoom_region_imshow
|
|
524
|
+
zoom_region_imshow=zoom_region_imshow,
|
|
525
|
+
aspect=aspect
|
|
476
526
|
)
|
|
477
527
|
ccdimage1_clean.mask[mask_data1c.astype(bool)] = True
|
|
478
528
|
ccdimage2_clean.mask[mask_data2c.astype(bool)] = True
|
|
@@ -495,7 +545,8 @@ def apply_cr2images_ccddata(infile1, infile2=None, outfile1=None, outfile2=None,
|
|
|
495
545
|
image_region=image_region,
|
|
496
546
|
return_masks=True,
|
|
497
547
|
debug_level=debug_level,
|
|
498
|
-
zoom_region_imshow=zoom_region_imshow
|
|
548
|
+
zoom_region_imshow=zoom_region_imshow,
|
|
549
|
+
aspect=aspect
|
|
499
550
|
)
|
|
500
551
|
ccdimage1_clean.mask[mask_data1c.astype(bool)] = True
|
|
501
552
|
ccdimage2_uncertainty_array = ndimage.median_filter(ccdimage1.uncertainty.array, size=median_size)
|
|
@@ -11,7 +11,7 @@
|
|
|
11
11
|
from datetime import datetime
|
|
12
12
|
import platform
|
|
13
13
|
import sys
|
|
14
|
-
from .version import
|
|
14
|
+
from .version import VERSION
|
|
15
15
|
|
|
16
16
|
|
|
17
17
|
def elapsed_time(time_ini, time_end, osinfo=True):
|
|
@@ -36,7 +36,7 @@ def elapsed_time(time_ini, time_end, osinfo=True):
|
|
|
36
36
|
print(f'node.............: {result.node}')
|
|
37
37
|
print(f'Python executable: {sys.executable}')
|
|
38
38
|
|
|
39
|
-
print(f"teareduce version: {
|
|
39
|
+
print(f"teareduce version: {VERSION}")
|
|
40
40
|
print(f"Initial time.....: {time_ini}")
|
|
41
41
|
print(f"Final time.......: {time_end}")
|
|
42
42
|
print(f"Elapsed time.....: {time_end - time_ini}")
|
|
@@ -54,4 +54,4 @@ def elapsed_time_since(time_ini, osinfo=True):
|
|
|
54
54
|
"""
|
|
55
55
|
|
|
56
56
|
time_end = datetime.now()
|
|
57
|
-
elapsed_time(time_ini, time_end)
|
|
57
|
+
elapsed_time(time_ini, time_end, osinfo=osinfo)
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
#
|
|
2
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
3
|
+
#
|
|
4
|
+
# This file is part of teareduce
|
|
5
|
+
#
|
|
6
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
7
|
+
# License-Filename: LICENSE.txt
|
|
8
|
+
#
|
|
9
|
+
|
|
10
|
+
"""Auxiliary function to display 1D histograms computed with numpy"""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def plot_hist_step(ax, bins, h, color='C0', alpha=1.0, fill_color=None, fill_alpha=0.4):
|
|
16
|
+
"""Plot histogram already computed.
|
|
17
|
+
|
|
18
|
+
Parameters
|
|
19
|
+
----------
|
|
20
|
+
ax : matplotlib.axes.Axes
|
|
21
|
+
Axes to plot on.
|
|
22
|
+
bins : np.ndarray
|
|
23
|
+
Array of bin edges.
|
|
24
|
+
h : np.ndarray
|
|
25
|
+
Array of histogram values.
|
|
26
|
+
color : str, optional
|
|
27
|
+
Color of the histogram line, by default 'C0'.
|
|
28
|
+
alpha : float, optional
|
|
29
|
+
Transparency of the histogram line, by default 1.0.
|
|
30
|
+
fill_color : str, optional
|
|
31
|
+
Color to fill the histogram area, by default None (no fill).
|
|
32
|
+
fill_alpha : float, optional
|
|
33
|
+
Transparency of the filled area, by default 0.4.
|
|
34
|
+
"""
|
|
35
|
+
# bin centers
|
|
36
|
+
xdum = (bins[:-1] + bins[1:]) / 2
|
|
37
|
+
ax.step(xdum, h, where='mid')
|
|
38
|
+
# draw vertical lines at the edges
|
|
39
|
+
ax.plot([bins[0], bins[0], xdum[0]], [0, h[0], h[0]], alpha=alpha, color=f'{color}', linestyle='-')
|
|
40
|
+
ax.plot([xdum[-1], bins[-1], bins[-1]], [h[-1], h[-1], 0], alpha=alpha, color=f'{color}', linestyle='-')
|
|
41
|
+
# fill area under the histogram
|
|
42
|
+
if fill_color is not None:
|
|
43
|
+
ax.fill_between(np.concatenate((np.array([bins[0]]), xdum, np.array([bins[-1]]))),
|
|
44
|
+
np.concatenate((np.array([h[0]]), h, np.array([h[-1]]))),
|
|
45
|
+
step='mid', alpha=fill_alpha, color=f'{fill_color}')
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def hist_step(ax, data, bins, color='C0', alpha=1.0, fill_color=None, fill_alpha=0.4):
|
|
49
|
+
"""Compute and plot histogram of data.
|
|
50
|
+
|
|
51
|
+
Parameters
|
|
52
|
+
----------
|
|
53
|
+
ax : matplotlib.axes.Axes
|
|
54
|
+
Axes to plot on.
|
|
55
|
+
data : np.ndarray
|
|
56
|
+
Data to compute the histogram from.
|
|
57
|
+
bins : int or np.ndarray
|
|
58
|
+
Number of bins or array of bin edges.
|
|
59
|
+
color : str, optional
|
|
60
|
+
Color of the histogram line, by default 'C0'.
|
|
61
|
+
alpha : float, optional
|
|
62
|
+
Transparency of the histogram line, by default 1.0.
|
|
63
|
+
fill_color : str, optional
|
|
64
|
+
Color to fill the histogram area, by default None (no fill).
|
|
65
|
+
fill_alpha : float, optional
|
|
66
|
+
Transparency of the filled area, by default 0.4.
|
|
67
|
+
|
|
68
|
+
Returns
|
|
69
|
+
-------
|
|
70
|
+
h : np.ndarray
|
|
71
|
+
Histogram values.
|
|
72
|
+
edges : np.ndarray
|
|
73
|
+
Bin edges of the histogram.
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
if isinstance(bins, int):
|
|
77
|
+
bins = np.linspace(np.min(data), np.max(data), bins + 1)
|
|
78
|
+
elif isinstance(bins, np.ndarray):
|
|
79
|
+
pass
|
|
80
|
+
else:
|
|
81
|
+
raise ValueError(f'Unexpected {bins=}')
|
|
82
|
+
h, edges = np.histogram(data, bins=bins)
|
|
83
|
+
plot_hist_step(ax, bins, h, color=color, alpha=alpha, fill_color=fill_color, fill_alpha=fill_alpha)
|
|
84
|
+
|
|
85
|
+
return h, edges
|
|
@@ -584,9 +584,9 @@ class SimulateCCDExposure:
|
|
|
584
584
|
|
|
585
585
|
# BIAS and Readout Noise
|
|
586
586
|
if np.isnan(self.bias.value).any():
|
|
587
|
-
raise ValueError(
|
|
587
|
+
raise ValueError("The parameter 'bias' contains NaN")
|
|
588
588
|
if np.isnan(self.readout_noise.value).any():
|
|
589
|
-
raise ValueError(
|
|
589
|
+
raise ValueError("The parameter 'readout_noise' contains NaN")
|
|
590
590
|
image2d = self._rng.normal(
|
|
591
591
|
loc=self.bias.value,
|
|
592
592
|
scale=self.readout_noise.value
|
|
@@ -597,7 +597,7 @@ class SimulateCCDExposure:
|
|
|
597
597
|
|
|
598
598
|
# DARK
|
|
599
599
|
if np.isnan(self.dark.value).any():
|
|
600
|
-
raise ValueError(
|
|
600
|
+
raise ValueError("The parameter 'dark' contains NaN")
|
|
601
601
|
image2d += self.dark.value
|
|
602
602
|
if imgtype == "dark":
|
|
603
603
|
result.data = image2d
|
|
@@ -605,11 +605,11 @@ class SimulateCCDExposure:
|
|
|
605
605
|
|
|
606
606
|
# OBJECT
|
|
607
607
|
if np.isnan(self.flatfield).any():
|
|
608
|
-
raise ValueError(
|
|
608
|
+
raise ValueError("The parameter 'flatfield' contains NaN")
|
|
609
609
|
if np.isnan(self.data_model.value).any():
|
|
610
|
-
raise ValueError(
|
|
610
|
+
raise ValueError("The parameter 'data_model' contains NaN")
|
|
611
611
|
if np.isnan(self.gain.value).any():
|
|
612
|
-
raise ValueError(
|
|
612
|
+
raise ValueError("The parameter 'gain' contains NaN")
|
|
613
613
|
if method.lower() == "poisson":
|
|
614
614
|
# transform data_model from ADU to electrons,
|
|
615
615
|
# generate Poisson distribution
|
|
@@ -6,10 +6,16 @@
|
|
|
6
6
|
# SPDX-License-Identifier: GPL-3.0+
|
|
7
7
|
# License-Filename: LICENSE.txt
|
|
8
8
|
#
|
|
9
|
+
"""Auxiliary classes to handle slicing regions in 1D, 2D, and 3D.
|
|
10
|
+
|
|
11
|
+
These classes provide a way to define and manipulate slices in a
|
|
12
|
+
consistent manner, following both FITS and Python conventions.
|
|
13
|
+
"""
|
|
9
14
|
|
|
10
|
-
import numpy as np
|
|
11
15
|
import re
|
|
12
16
|
|
|
17
|
+
import numpy as np
|
|
18
|
+
|
|
13
19
|
class SliceRegion1D:
|
|
14
20
|
"""Store indices for slicing of 1D regions.
|
|
15
21
|
|
|
@@ -35,7 +41,7 @@ class SliceRegion1D:
|
|
|
35
41
|
within(other)
|
|
36
42
|
Check if slice 'other' is within the parent slice.
|
|
37
43
|
"""
|
|
38
|
-
|
|
44
|
+
|
|
39
45
|
def __init__(self, region, mode=None):
|
|
40
46
|
"""Initialize SliceRegion1D.
|
|
41
47
|
|
|
@@ -58,21 +64,21 @@ class SliceRegion1D:
|
|
|
58
64
|
region = np.s_[numbers_int[0]:numbers_int[1]]
|
|
59
65
|
|
|
60
66
|
if isinstance(region, slice):
|
|
61
|
-
for number in [
|
|
67
|
+
for number in [region.start, region.stop]:
|
|
62
68
|
if number is None:
|
|
63
|
-
raise ValueError(f'Invalid {
|
|
69
|
+
raise ValueError(f'Invalid {region!r}: you must specify start:stop in slice by number')
|
|
64
70
|
else:
|
|
65
|
-
raise ValueError(f'Object {region} of type {type(region)} is not a slice')
|
|
66
|
-
|
|
71
|
+
raise ValueError(f'Object {region} of type {type(region)} is not a slice')
|
|
72
|
+
|
|
67
73
|
if region.step not in [1, None]:
|
|
68
74
|
raise ValueError(f'This class {self.__class__.__name__} '
|
|
69
75
|
'does not handle step != 1')
|
|
70
|
-
|
|
76
|
+
|
|
71
77
|
errmsg = f'Invalid mode={mode}. Only "FITS" or "Python" (case insensitive) are valid'
|
|
72
78
|
if mode is None:
|
|
73
79
|
raise ValueError(errmsg)
|
|
74
80
|
self.mode = mode.lower()
|
|
75
|
-
|
|
81
|
+
|
|
76
82
|
if self.mode == 'fits':
|
|
77
83
|
if region.stop < region.start:
|
|
78
84
|
raise ValueError(f'Invalid {region!r}')
|
|
@@ -129,7 +135,7 @@ class SliceRegion1D:
|
|
|
129
135
|
return result
|
|
130
136
|
result = True
|
|
131
137
|
return result
|
|
132
|
-
|
|
138
|
+
|
|
133
139
|
|
|
134
140
|
class SliceRegion2D:
|
|
135
141
|
"""Store indices for slicing of 2D regions.
|
|
@@ -140,9 +146,9 @@ class SliceRegion2D:
|
|
|
140
146
|
Attributes
|
|
141
147
|
----------
|
|
142
148
|
fits : slice
|
|
143
|
-
|
|
149
|
+
2D slice following the FITS convention.
|
|
144
150
|
python : slice
|
|
145
|
-
|
|
151
|
+
2D slice following the Python convention.
|
|
146
152
|
mode : str
|
|
147
153
|
Convention mode employed to define the slice.
|
|
148
154
|
The two possible modes are 'fits' and 'python'.
|
|
@@ -157,7 +163,7 @@ class SliceRegion2D:
|
|
|
157
163
|
Check if slice 'other' is within the parent slice."""
|
|
158
164
|
|
|
159
165
|
def __init__(self, region, mode=None):
|
|
160
|
-
"""Initialize
|
|
166
|
+
"""Initialize SliceRegion2D.
|
|
161
167
|
|
|
162
168
|
Parameters
|
|
163
169
|
----------
|
|
@@ -182,13 +188,13 @@ class SliceRegion2D:
|
|
|
182
188
|
s1, s2 = region
|
|
183
189
|
for item in [s1, s2]:
|
|
184
190
|
if isinstance(item, slice):
|
|
185
|
-
for number in [
|
|
191
|
+
for number in [item.start, item.stop]:
|
|
186
192
|
if number is None:
|
|
187
193
|
raise ValueError(f'Invalid {item!r}: you must specify start:stop in slice by number')
|
|
194
|
+
if item.step not in [1, None]:
|
|
195
|
+
raise ValueError(f'This class {self.__class__.__name__} does not handle step != 1')
|
|
188
196
|
else:
|
|
189
197
|
raise ValueError(f'Object {item} of type {type(item)} is not a slice')
|
|
190
|
-
if s1.step not in [1, None] or s2.step not in [1, None]:
|
|
191
|
-
raise ValueError(f'This class {self.__class__.__name__} does not handle step != 1')
|
|
192
198
|
else:
|
|
193
199
|
raise ValueError(f'This class {self.__class__.__name__} only handles 2D regions')
|
|
194
200
|
|
|
@@ -261,3 +267,143 @@ class SliceRegion2D:
|
|
|
261
267
|
return result
|
|
262
268
|
result = True
|
|
263
269
|
return result
|
|
270
|
+
|
|
271
|
+
|
|
272
|
+
class SliceRegion3D:
|
|
273
|
+
"""Store indices for slicing of 3D regions.
|
|
274
|
+
|
|
275
|
+
The attributes .python and .fits provide the indices following
|
|
276
|
+
the Python and the FITS convention, respectively.
|
|
277
|
+
|
|
278
|
+
Attributes
|
|
279
|
+
----------
|
|
280
|
+
fits : slice
|
|
281
|
+
3D slice following the FITS convention.
|
|
282
|
+
python : slice
|
|
283
|
+
3D slice following the Python convention.
|
|
284
|
+
mode : str
|
|
285
|
+
Convention mode employed to define the slice.
|
|
286
|
+
The two possible modes are 'fits' and 'python'.
|
|
287
|
+
fits_section : str
|
|
288
|
+
Resulting slice section in FITS convention:
|
|
289
|
+
'[num1:num2,num3:num4,num5:num6]'. This string is defined after
|
|
290
|
+
successfully initializing the SliceRegion3D instance.
|
|
291
|
+
|
|
292
|
+
Methods
|
|
293
|
+
-------
|
|
294
|
+
within(other)
|
|
295
|
+
Check if slice 'other' is within the parent slice."""
|
|
296
|
+
|
|
297
|
+
def __init__(self, region, mode=None):
|
|
298
|
+
"""Initialize SliceRegion3D.
|
|
299
|
+
|
|
300
|
+
Parameters
|
|
301
|
+
----------
|
|
302
|
+
region : slice or str
|
|
303
|
+
Slice region. It can be provided as np.s_[num1:num2, num3:num4, num5:num6],
|
|
304
|
+
as a tuple (slice(num1, num2), slice(num3, num4), slice(num5, num6)),
|
|
305
|
+
or as a string '[num1:num2, num3:num4, num5:num6]'
|
|
306
|
+
mode : str
|
|
307
|
+
Convention mode employed to define the slice.
|
|
308
|
+
The two possible modes are 'fits' and 'python'.
|
|
309
|
+
"""
|
|
310
|
+
if isinstance(region, str):
|
|
311
|
+
pattern = r'^\s*\[\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*,\s*\d+\s*:\s*\d+\s*\]\s*$'
|
|
312
|
+
if not re.match(pattern, region):
|
|
313
|
+
raise ValueError(f"Invalid {region!r}. It must match '[num:num, num:num, num:num]'")
|
|
314
|
+
# extract numbers and generate np.s_[num:num, num:num, num:num]
|
|
315
|
+
numbers_str = re.findall(r'\d+', region)
|
|
316
|
+
numbers_int = list(map(int, numbers_str))
|
|
317
|
+
region = np.s_[numbers_int[0]:numbers_int[1], numbers_int[2]:numbers_int[3], numbers_int[4]:numbers_int[5]]
|
|
318
|
+
|
|
319
|
+
if isinstance(region, tuple) and len(region) == 3:
|
|
320
|
+
s1, s2, s3 = region
|
|
321
|
+
for item in [s1, s2, s3]:
|
|
322
|
+
if isinstance(item, slice):
|
|
323
|
+
for number in [item.start, item.stop]:
|
|
324
|
+
if number is None:
|
|
325
|
+
raise ValueError(f'Invalid {item!r}: you must specify start:stop in slice by number')
|
|
326
|
+
if item.step not in [1, None]:
|
|
327
|
+
raise ValueError(f'This class {self.__class__.__name__} does not handle step != 1')
|
|
328
|
+
else:
|
|
329
|
+
raise ValueError(f'Object {item} of type {type(item)} is not a slice')
|
|
330
|
+
else:
|
|
331
|
+
raise ValueError(f'This class {self.__class__.__name__} only handles 3D regions')
|
|
332
|
+
|
|
333
|
+
errmsg = f'Invalid mode={mode}. Only "FITS" or "Python" (case insensitive) are valid'
|
|
334
|
+
if mode is None:
|
|
335
|
+
raise ValueError(errmsg)
|
|
336
|
+
self.mode = mode.lower()
|
|
337
|
+
|
|
338
|
+
if self.mode == 'fits':
|
|
339
|
+
if s1.stop < s1.start:
|
|
340
|
+
raise ValueError(f'Invalid {s1!r}')
|
|
341
|
+
if s2.stop < s2.start:
|
|
342
|
+
raise ValueError(f'Invalid {s2!r}')
|
|
343
|
+
if s3.stop < s3.start:
|
|
344
|
+
raise ValueError(f'Invalid {s3!r}')
|
|
345
|
+
self.fits = region
|
|
346
|
+
self.python = slice(s3.start-1, s3.stop), slice(s2.start-1, s2.stop), slice(s1.start-1, s1.stop)
|
|
347
|
+
elif self.mode == 'python':
|
|
348
|
+
if s1.stop <= s1.start:
|
|
349
|
+
raise ValueError(f'Invalid {s1!r}')
|
|
350
|
+
if s2.stop <= s2.start:
|
|
351
|
+
raise ValueError(f'Invalid {s2!r}')
|
|
352
|
+
if s3.stop <= s3.start:
|
|
353
|
+
raise ValueError(f'Invalid {s3!r}')
|
|
354
|
+
self.fits = slice(s3.start+1, s3.stop), slice(s2.start+1, s2.stop), slice(s1.start+1, s1.stop)
|
|
355
|
+
self.python = region
|
|
356
|
+
else:
|
|
357
|
+
raise ValueError(errmsg)
|
|
358
|
+
|
|
359
|
+
s1, s2, s3 = self.fits
|
|
360
|
+
self.fits_section = f'[{s1.start}:{s1.stop},{s2.start}:{s2.stop},{s3.start}:{s3.stop}]'
|
|
361
|
+
|
|
362
|
+
def __eq__(self, other):
|
|
363
|
+
return self.fits == other.fits and self.python == other.python
|
|
364
|
+
|
|
365
|
+
def __repr__(self):
|
|
366
|
+
if self.mode == 'fits':
|
|
367
|
+
return (f'{self.__class__.__name__}('
|
|
368
|
+
f'{self.fits!r}, mode="fits")')
|
|
369
|
+
else:
|
|
370
|
+
return (f'{self.__class__.__name__}('
|
|
371
|
+
f'{self.python!r}, mode="python")')
|
|
372
|
+
|
|
373
|
+
def within(self, other):
|
|
374
|
+
"""Determine if slice 'other' is within the parent slice.
|
|
375
|
+
|
|
376
|
+
Parameters
|
|
377
|
+
----------
|
|
378
|
+
other : SliceRegion3D
|
|
379
|
+
New instance for which we want to determine
|
|
380
|
+
if it is within the parent SliceRegion3D instance.
|
|
381
|
+
|
|
382
|
+
Returns
|
|
383
|
+
-------
|
|
384
|
+
result : bool
|
|
385
|
+
Return True if 'other' is within the parent slice.
|
|
386
|
+
False otherwise.
|
|
387
|
+
"""
|
|
388
|
+
if isinstance(other, self.__class__):
|
|
389
|
+
pass
|
|
390
|
+
else:
|
|
391
|
+
raise ValueError(f'Object {other} of type {type(other)} is not a {self.__class__.__name__}')
|
|
392
|
+
|
|
393
|
+
s1, s2, s3 = self.python
|
|
394
|
+
s1_other, s2_other, s3_other = other.python
|
|
395
|
+
result = False
|
|
396
|
+
if s1.start < s1_other.start:
|
|
397
|
+
return result
|
|
398
|
+
if s1.stop > s1_other.stop:
|
|
399
|
+
return result
|
|
400
|
+
if s2.start < s2_other.start:
|
|
401
|
+
return result
|
|
402
|
+
if s2.stop > s2_other.stop:
|
|
403
|
+
return result
|
|
404
|
+
if s3.start < s3_other.start:
|
|
405
|
+
return result
|
|
406
|
+
if s3.stop > s3_other.stop:
|
|
407
|
+
return result
|
|
408
|
+
result = True
|
|
409
|
+
return result
|
|
File without changes
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2025 Universidad Complutense de Madrid
|
|
4
|
+
#
|
|
5
|
+
# This file is part of teareduce
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
8
|
+
# License-Filename: LICENSE.txt
|
|
9
|
+
#
|
|
10
|
+
"""Tests for the SliceRegion class."""
|
|
11
|
+
|
|
12
|
+
import numpy as np
|
|
13
|
+
|
|
14
|
+
from ..sliceregion import SliceRegion1D, SliceRegion2D, SliceRegion3D
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
def test_slice_region_creation():
|
|
18
|
+
"""Test the creation of a SliceRegion."""
|
|
19
|
+
|
|
20
|
+
region1d = SliceRegion1D(np.s_[1:10], mode='python')
|
|
21
|
+
assert region1d.fits == slice(2, 10, None)
|
|
22
|
+
assert region1d.python == slice(1, 10, None)
|
|
23
|
+
assert region1d.fits_section == '[2:10]'
|
|
24
|
+
|
|
25
|
+
region2d = SliceRegion2D(np.s_[1:10, 2:20], mode='python')
|
|
26
|
+
assert region2d.fits == (slice(3, 20, None), slice(2, 10, None))
|
|
27
|
+
assert region2d.python == (slice(1, 10, None), slice(2, 20, None))
|
|
28
|
+
assert region2d.fits_section == '[3:20,2:10]'
|
|
29
|
+
|
|
30
|
+
region3d = SliceRegion3D(np.s_[1:10, 2:20, 3:30], mode='python')
|
|
31
|
+
assert region3d.fits == (slice(4, 30, None), slice(3, 20, None), slice(2, 10, None))
|
|
32
|
+
assert region3d.python == (slice(1, 10, None), slice(2, 20, None), slice(3, 30, None))
|
|
33
|
+
assert region3d.fits_section == '[4:30,3:20,2:10]'
|
|
34
|
+
|
|
35
|
+
def test_slice_values():
|
|
36
|
+
"""Test the values of the slices in different modes."""
|
|
37
|
+
|
|
38
|
+
array1d = np.arange(10)
|
|
39
|
+
|
|
40
|
+
region1d = SliceRegion1D(np.s_[1:3], mode='python')
|
|
41
|
+
assert np.all(array1d[region1d.python] == np.array([1, 2]))
|
|
42
|
+
|
|
43
|
+
array2d = np.arange(12).reshape(3, 4)
|
|
44
|
+
region2d = SliceRegion2D(np.s_[1:3, 2:3], mode='python')
|
|
45
|
+
assert np.all(array2d[region2d.python] == np.array([[6], [10]]))
|
|
46
|
+
|
|
47
|
+
array3d = np.arange(24).reshape(3, 4, 2)
|
|
48
|
+
region3d = SliceRegion3D(np.s_[1:3, 2:4, 1:2], mode='python')
|
|
49
|
+
assert np.all(array3d[region3d.python] == np.array([[[13], [15]], [[21], [23]]]))
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# -*- coding: utf-8 -*-
|
|
2
|
+
#
|
|
3
|
+
# Copyright 2023-2025 Universidad Complutense de Madrid
|
|
4
|
+
#
|
|
5
|
+
# This file is part of teareduce
|
|
6
|
+
#
|
|
7
|
+
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
8
|
+
# License-Filename: LICENSE.txt
|
|
9
|
+
#
|
|
10
|
+
"""Module to define the version of the teareduce package."""
|
|
11
|
+
|
|
12
|
+
VERSION = '0.4.3'
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def main():
|
|
16
|
+
"""Prints the version of the teareduce package."""
|
|
17
|
+
print('Version: ' + VERSION)
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
if __name__ == "__main__":
|
|
21
|
+
main()
|
|
@@ -1,34 +1,33 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: teareduce
|
|
3
|
-
Version: 0.4.
|
|
3
|
+
Version: 0.4.3
|
|
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
|
|
7
7
|
Project-URL: Homepage, https://github.com/nicocardiel/teareduce
|
|
8
8
|
Project-URL: Repository, https://github.com/nicocardiel/teareduce.git
|
|
9
|
-
Classifier: Programming Language :: Python :: 3.8
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.9
|
|
11
9
|
Classifier: Programming Language :: Python :: 3.10
|
|
12
10
|
Classifier: Programming Language :: Python :: 3.11
|
|
13
11
|
Classifier: Programming Language :: Python :: 3.12
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
14
13
|
Classifier: Development Status :: 3 - Alpha
|
|
15
14
|
Classifier: Environment :: Console
|
|
16
15
|
Classifier: Intended Audience :: Science/Research
|
|
17
|
-
Classifier: License :: OSI Approved :: GNU General Public License (
|
|
16
|
+
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
18
17
|
Classifier: Operating System :: OS Independent
|
|
19
18
|
Classifier: Topic :: Scientific/Engineering :: Astronomy
|
|
20
|
-
Requires-Python: >=3.
|
|
19
|
+
Requires-Python: >=3.10
|
|
21
20
|
Description-Content-Type: text/markdown
|
|
22
21
|
License-File: LICENSE.txt
|
|
23
22
|
Requires-Dist: astropy
|
|
24
|
-
Requires-Dist: importlib_resources
|
|
25
23
|
Requires-Dist: lmfit
|
|
26
24
|
Requires-Dist: matplotlib
|
|
27
|
-
Requires-Dist: numpy>=1.
|
|
25
|
+
Requires-Dist: numpy>=1.22
|
|
28
26
|
Requires-Dist: scipy
|
|
29
27
|
Requires-Dist: tqdm
|
|
30
28
|
Provides-Extra: test
|
|
31
29
|
Requires-Dist: pytest; extra == "test"
|
|
30
|
+
Dynamic: license-file
|
|
32
31
|
|
|
33
32
|
# teareduce
|
|
34
33
|
Utilities for astronomical data reduction
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
LICENSE.txt
|
|
2
2
|
README.md
|
|
3
3
|
pyproject.toml
|
|
4
|
-
src/teareduce/SimulateCCDExposure.py
|
|
5
4
|
src/teareduce/__init__.py
|
|
6
5
|
src/teareduce/avoid_astropy_warnings.py
|
|
7
6
|
src/teareduce/correct_pincushion_distortion.py
|
|
@@ -9,6 +8,7 @@ src/teareduce/cosmicrays.py
|
|
|
9
8
|
src/teareduce/ctext.py
|
|
10
9
|
src/teareduce/draw_rectangle.py
|
|
11
10
|
src/teareduce/elapsed_time.py
|
|
11
|
+
src/teareduce/histogram1d.py
|
|
12
12
|
src/teareduce/imshow.py
|
|
13
13
|
src/teareduce/numsplines.py
|
|
14
14
|
src/teareduce/peaks_spectrum.py
|
|
@@ -26,4 +26,6 @@ src/teareduce.egg-info/PKG-INFO
|
|
|
26
26
|
src/teareduce.egg-info/SOURCES.txt
|
|
27
27
|
src/teareduce.egg-info/dependency_links.txt
|
|
28
28
|
src/teareduce.egg-info/requires.txt
|
|
29
|
-
src/teareduce.egg-info/top_level.txt
|
|
29
|
+
src/teareduce.egg-info/top_level.txt
|
|
30
|
+
src/teareduce/tests/__init__.py
|
|
31
|
+
src/teareduce/tests/test_sliceregion.py
|
|
@@ -1,19 +0,0 @@
|
|
|
1
|
-
# -*- coding: utf-8 -*-
|
|
2
|
-
#
|
|
3
|
-
# Copyright 2023-2024 Universidad Complutense de Madrid
|
|
4
|
-
#
|
|
5
|
-
# This file is part of teareduce
|
|
6
|
-
#
|
|
7
|
-
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
8
|
-
# License-Filename: LICENSE.txt
|
|
9
|
-
#
|
|
10
|
-
|
|
11
|
-
version = '0.4.1'
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
def main():
|
|
15
|
-
print('Version: ' + version)
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
if __name__ == "__main__":
|
|
19
|
-
main()
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|