ecallistolib 0.2.1__py3-none-any.whl → 0.2.3__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.
- ecallistolib/__init__.py +6 -1
- ecallistolib/combine.py +1 -1
- ecallistolib/crop.py +1 -1
- ecallistolib/download.py +1 -1
- ecallistolib/exceptions.py +6 -1
- ecallistolib/io.py +1 -1
- ecallistolib/models.py +1 -1
- ecallistolib/plotting.py +304 -92
- ecallistolib/processing.py +3 -3
- {ecallistolib-0.2.1.dist-info → ecallistolib-0.2.3.dist-info}/METADATA +186 -71
- ecallistolib-0.2.3.dist-info/RECORD +14 -0
- {ecallistolib-0.2.1.dist-info → ecallistolib-0.2.3.dist-info}/WHEEL +1 -1
- ecallistolib-0.2.1.dist-info/RECORD +0 -14
- {ecallistolib-0.2.1.dist-info → ecallistolib-0.2.3.dist-info}/licenses/LICENSE +0 -0
- {ecallistolib-0.2.1.dist-info → ecallistolib-0.2.3.dist-info}/top_level.txt +0 -0
ecallistolib/__init__.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
|
|
2
2
|
"""
|
|
3
3
|
e-callistolib: Tools for e-CALLISTO FITS dynamic spectra.
|
|
4
|
-
Version 0.2.
|
|
4
|
+
Version 0.2.3
|
|
5
5
|
Sahan S Liyanage (sahanslst@gmail.com)
|
|
6
6
|
Astronomical and Space Science Unit, University of Colombo, Sri Lanka.
|
|
7
7
|
"""
|
|
@@ -14,6 +14,7 @@ from .exceptions import (
|
|
|
14
14
|
CropError,
|
|
15
15
|
DownloadError,
|
|
16
16
|
ECallistoError,
|
|
17
|
+
FrequencyOutOfRangeError,
|
|
17
18
|
InvalidFilenameError,
|
|
18
19
|
InvalidFITSError,
|
|
19
20
|
)
|
|
@@ -50,6 +51,7 @@ __all__ = [
|
|
|
50
51
|
"DownloadError",
|
|
51
52
|
"CombineError",
|
|
52
53
|
"CropError",
|
|
54
|
+
"FrequencyOutOfRangeError",
|
|
53
55
|
]
|
|
54
56
|
|
|
55
57
|
|
|
@@ -86,12 +88,14 @@ def __getattr__(name: str):
|
|
|
86
88
|
"plot_dynamic_spectrum",
|
|
87
89
|
"plot_raw_spectrum",
|
|
88
90
|
"plot_background_subtracted",
|
|
91
|
+
"plot_light_curve",
|
|
89
92
|
"TimeAxisConverter",
|
|
90
93
|
}:
|
|
91
94
|
from .plotting import (
|
|
92
95
|
TimeAxisConverter,
|
|
93
96
|
plot_background_subtracted,
|
|
94
97
|
plot_dynamic_spectrum,
|
|
98
|
+
plot_light_curve,
|
|
95
99
|
plot_raw_spectrum,
|
|
96
100
|
)
|
|
97
101
|
|
|
@@ -99,6 +103,7 @@ def __getattr__(name: str):
|
|
|
99
103
|
"plot_dynamic_spectrum": plot_dynamic_spectrum,
|
|
100
104
|
"plot_raw_spectrum": plot_raw_spectrum,
|
|
101
105
|
"plot_background_subtracted": plot_background_subtracted,
|
|
106
|
+
"plot_light_curve": plot_light_curve,
|
|
102
107
|
"TimeAxisConverter": TimeAxisConverter,
|
|
103
108
|
}[name]
|
|
104
109
|
|
ecallistolib/combine.py
CHANGED
ecallistolib/crop.py
CHANGED
ecallistolib/download.py
CHANGED
ecallistolib/exceptions.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
e-callistolib: Tools for e-CALLISTO FITS dynamic spectra.
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.3
|
|
4
4
|
Sahan S Liyanage (sahanslst@gmail.com)
|
|
5
5
|
Astronomical and Space Science Unit, University of Colombo, Sri Lanka.
|
|
6
6
|
"""
|
|
@@ -36,3 +36,8 @@ class CombineError(ECallistoError):
|
|
|
36
36
|
class CropError(ECallistoError):
|
|
37
37
|
"""Raised when cropping parameters are invalid."""
|
|
38
38
|
pass
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class FrequencyOutOfRangeError(ECallistoError):
|
|
42
|
+
"""Raised when the requested frequency is outside the spectrum's frequency range."""
|
|
43
|
+
pass
|
ecallistolib/io.py
CHANGED
ecallistolib/models.py
CHANGED
ecallistolib/plotting.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
e-callistolib: Tools for e-CALLISTO FITS dynamic spectra.
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.3
|
|
4
4
|
Sahan S Liyanage (sahanslst@gmail.com)
|
|
5
5
|
Astronomical and Space Science Unit, University of Colombo, Sri Lanka.
|
|
6
6
|
"""
|
|
@@ -155,39 +155,60 @@ def _get_filename_title(ds: DynamicSpectrum, suffix: str) -> str:
|
|
|
155
155
|
"""Generate plot title from DynamicSpectrum source filename."""
|
|
156
156
|
if ds.source is not None:
|
|
157
157
|
filename = ds.source.stem # Get filename without extension
|
|
158
|
+
# Also strip .fit if it's a double extension like .fit.gz
|
|
159
|
+
if filename.endswith(".fit"):
|
|
160
|
+
filename = filename[:-4]
|
|
158
161
|
return f"{filename}_{suffix}"
|
|
159
162
|
return suffix
|
|
160
163
|
|
|
161
164
|
|
|
162
|
-
|
|
165
|
+
# Conversion factor for Digits to dB (pseudo-calibration)
|
|
166
|
+
# dB = Digits * 2500 / 256 / 25.4 = Digits * 0.384
|
|
167
|
+
DIGITS_TO_DB_FACTOR = 2500.0 / 256.0 / 25.4 # ~0.384
|
|
168
|
+
|
|
169
|
+
|
|
170
|
+
def plot_dynamic_spectrum(
|
|
163
171
|
ds: DynamicSpectrum,
|
|
172
|
+
process: Literal["raw", "background_subtracted", "noise_reduced"] = "raw",
|
|
173
|
+
clip_low: float | None = None,
|
|
174
|
+
clip_high: float | None = None,
|
|
164
175
|
title: str | None = None,
|
|
165
|
-
cmap: str = "
|
|
176
|
+
cmap: str = "inferno",
|
|
166
177
|
figsize: tuple[float, float] | None = None,
|
|
167
|
-
vmin: float | None = None,
|
|
168
|
-
vmax: float | None = None,
|
|
169
178
|
ax: Optional[plt.Axes] = None,
|
|
170
179
|
show_colorbar: bool = True,
|
|
171
180
|
time_format: Literal["seconds", "ut"] = "seconds",
|
|
181
|
+
intensity_units: Literal["digits", "dB"] = "digits",
|
|
172
182
|
**imshow_kwargs,
|
|
173
183
|
) -> tuple["Figure", "Axes", "AxesImage"]:
|
|
174
184
|
"""
|
|
175
|
-
Plot a
|
|
185
|
+
Plot a DynamicSpectrum with selectable processing mode.
|
|
186
|
+
|
|
187
|
+
This is the main plotting function that supports raw, background-subtracted,
|
|
188
|
+
and noise-reduced visualization modes with full matplotlib customization.
|
|
176
189
|
|
|
177
190
|
Parameters
|
|
178
191
|
----------
|
|
179
192
|
ds : DynamicSpectrum
|
|
180
193
|
The dynamic spectrum to plot.
|
|
181
|
-
|
|
182
|
-
|
|
194
|
+
process : {"raw", "background_subtracted", "noise_reduced"}
|
|
195
|
+
Processing mode to apply before plotting:
|
|
196
|
+
- "raw": Plot the original data without any processing.
|
|
197
|
+
- "background_subtracted": Subtract mean over time for each frequency.
|
|
198
|
+
- "noise_reduced": Apply background subtraction and clipping (requires
|
|
199
|
+
clip_low and clip_high).
|
|
200
|
+
clip_low : float | None
|
|
201
|
+
Lower clipping threshold. Required when process="noise_reduced".
|
|
202
|
+
Also used for colormap normalization in all modes if provided.
|
|
203
|
+
clip_high : float | None
|
|
204
|
+
Upper clipping threshold. Required when process="noise_reduced".
|
|
205
|
+
Also used for colormap normalization in all modes if provided.
|
|
206
|
+
title : str | None
|
|
207
|
+
Plot title. If None, auto-generates from filename and process mode.
|
|
183
208
|
cmap : str
|
|
184
|
-
Matplotlib colormap name.
|
|
209
|
+
Matplotlib colormap name (e.g., "inferno", "viridis", "jet").
|
|
185
210
|
figsize : tuple[float, float] | None
|
|
186
211
|
Figure size as (width, height) in inches. Ignored if ax is provided.
|
|
187
|
-
vmin : float | None
|
|
188
|
-
Minimum value for colormap normalization.
|
|
189
|
-
vmax : float | None
|
|
190
|
-
Maximum value for colormap normalization.
|
|
191
212
|
ax : plt.Axes | None
|
|
192
213
|
Existing axes to plot on. If None, creates a new figure.
|
|
193
214
|
show_colorbar : bool
|
|
@@ -195,19 +216,60 @@ def plot_raw_spectrum(
|
|
|
195
216
|
time_format : {"seconds", "ut"}
|
|
196
217
|
Format for the time axis. "seconds" shows elapsed seconds,
|
|
197
218
|
"ut" shows Universal Time (requires ut_start_sec in metadata).
|
|
219
|
+
intensity_units : {"digits", "dB"}
|
|
220
|
+
Units for the intensity axis. "digits" shows raw ADU values,
|
|
221
|
+
"dB" converts using dB = Digits * 0.384 (pseudo-calibration).
|
|
198
222
|
**imshow_kwargs
|
|
199
223
|
Additional keyword arguments passed to matplotlib's imshow().
|
|
224
|
+
Common options include:
|
|
225
|
+
- interpolation: str ("nearest", "bilinear", "bicubic", etc.)
|
|
226
|
+
- origin: str ("upper", "lower")
|
|
227
|
+
- alpha: float (transparency)
|
|
200
228
|
|
|
201
229
|
Returns
|
|
202
230
|
-------
|
|
203
231
|
tuple[Figure, Axes, AxesImage]
|
|
204
232
|
The figure, axes, and image objects.
|
|
205
233
|
|
|
234
|
+
Raises
|
|
235
|
+
------
|
|
236
|
+
ValueError
|
|
237
|
+
If process="noise_reduced" but clip_low or clip_high is not provided.
|
|
238
|
+
|
|
206
239
|
Example
|
|
207
240
|
-------
|
|
208
241
|
>>> ds = read_fits("spectrum.fit.gz")
|
|
209
|
-
>>>
|
|
242
|
+
>>> # Plot raw spectrum
|
|
243
|
+
>>> fig, ax, im = plot_dynamic_spectrum(ds, process="raw")
|
|
244
|
+
>>> # Plot noise-reduced spectrum
|
|
245
|
+
>>> fig, ax, im = plot_dynamic_spectrum(
|
|
246
|
+
... ds, process="noise_reduced",
|
|
247
|
+
... clip_low=-5, clip_high=20,
|
|
248
|
+
... cmap="jet"
|
|
249
|
+
... )
|
|
210
250
|
"""
|
|
251
|
+
from .processing import background_subtract, noise_reduce_mean_clip
|
|
252
|
+
|
|
253
|
+
# Validate parameters for noise_reduced mode
|
|
254
|
+
if process == "noise_reduced":
|
|
255
|
+
if clip_low is None or clip_high is None:
|
|
256
|
+
raise ValueError(
|
|
257
|
+
"When process='noise_reduced', both clip_low and clip_high must be provided."
|
|
258
|
+
)
|
|
259
|
+
|
|
260
|
+
# Apply processing
|
|
261
|
+
if process == "background_subtracted":
|
|
262
|
+
ds_plot = background_subtract(ds)
|
|
263
|
+
title_suffix = "background_subtracted"
|
|
264
|
+
elif process == "noise_reduced":
|
|
265
|
+
ds_plot = noise_reduce_mean_clip(
|
|
266
|
+
ds, clip_low=clip_low, clip_high=clip_high, scale=None
|
|
267
|
+
)
|
|
268
|
+
title_suffix = "noise_clipped"
|
|
269
|
+
else: # raw
|
|
270
|
+
ds_plot = ds
|
|
271
|
+
title_suffix = "raw"
|
|
272
|
+
|
|
211
273
|
if ax is None:
|
|
212
274
|
fig, ax = plt.subplots(figsize=figsize)
|
|
213
275
|
else:
|
|
@@ -215,8 +277,18 @@ def plot_raw_spectrum(
|
|
|
215
277
|
|
|
216
278
|
extent, converter = _compute_extent(ds, time_format)
|
|
217
279
|
|
|
280
|
+
# Convert to dB if requested
|
|
281
|
+
plot_data = ds_plot.data
|
|
282
|
+
vmin, vmax = clip_low, clip_high
|
|
283
|
+
if intensity_units == "dB":
|
|
284
|
+
plot_data = plot_data * DIGITS_TO_DB_FACTOR
|
|
285
|
+
if vmin is not None:
|
|
286
|
+
vmin = vmin * DIGITS_TO_DB_FACTOR
|
|
287
|
+
if vmax is not None:
|
|
288
|
+
vmax = vmax * DIGITS_TO_DB_FACTOR
|
|
289
|
+
|
|
218
290
|
im = ax.imshow(
|
|
219
|
-
|
|
291
|
+
plot_data,
|
|
220
292
|
aspect="auto",
|
|
221
293
|
extent=extent,
|
|
222
294
|
cmap=cmap,
|
|
@@ -226,65 +298,64 @@ def plot_raw_spectrum(
|
|
|
226
298
|
)
|
|
227
299
|
# Use filename-based title if not provided
|
|
228
300
|
if title is None:
|
|
229
|
-
title = _get_filename_title(ds,
|
|
301
|
+
title = _get_filename_title(ds, title_suffix)
|
|
230
302
|
ax.set_title(title)
|
|
231
303
|
_format_time_axis(ax, converter, time_format)
|
|
232
304
|
ax.set_ylabel("Frequency [MHz]")
|
|
233
305
|
|
|
234
306
|
if show_colorbar:
|
|
235
307
|
cbar = fig.colorbar(im, ax=ax)
|
|
236
|
-
|
|
308
|
+
if intensity_units == "dB":
|
|
309
|
+
cbar.set_label("Intensity [dB]")
|
|
310
|
+
else:
|
|
311
|
+
cbar.set_label("Intensity [Digits]")
|
|
237
312
|
|
|
238
313
|
return fig, ax, im
|
|
239
314
|
|
|
240
315
|
|
|
241
|
-
def
|
|
316
|
+
def plot_raw_spectrum(
|
|
242
317
|
ds: DynamicSpectrum,
|
|
243
318
|
title: str | None = None,
|
|
244
|
-
cmap: str = "
|
|
319
|
+
cmap: str = "viridis",
|
|
245
320
|
figsize: tuple[float, float] | None = None,
|
|
246
|
-
|
|
247
|
-
|
|
321
|
+
clip_low: float | None = None,
|
|
322
|
+
clip_high: float | None = None,
|
|
248
323
|
ax: Optional[plt.Axes] = None,
|
|
249
324
|
show_colorbar: bool = True,
|
|
250
325
|
time_format: Literal["seconds", "ut"] = "seconds",
|
|
326
|
+
intensity_units: Literal["digits", "dB"] = "digits",
|
|
251
327
|
**imshow_kwargs,
|
|
252
328
|
) -> tuple["Figure", "Axes", "AxesImage"]:
|
|
253
329
|
"""
|
|
254
|
-
Plot a DynamicSpectrum
|
|
330
|
+
Plot a raw DynamicSpectrum without any processing.
|
|
255
331
|
|
|
256
|
-
This is
|
|
257
|
-
|
|
258
|
-
and custom time axis formats.
|
|
332
|
+
This is a convenience function that calls plot_dynamic_spectrum with
|
|
333
|
+
process="raw" and a default colormap suitable for raw data.
|
|
259
334
|
|
|
260
335
|
Parameters
|
|
261
336
|
----------
|
|
262
337
|
ds : DynamicSpectrum
|
|
263
338
|
The dynamic spectrum to plot.
|
|
264
|
-
title : str
|
|
339
|
+
title : str | None
|
|
265
340
|
Plot title.
|
|
266
341
|
cmap : str
|
|
267
|
-
Matplotlib colormap name
|
|
342
|
+
Matplotlib colormap name. Default is "viridis".
|
|
268
343
|
figsize : tuple[float, float] | None
|
|
269
|
-
Figure size as (width, height) in inches.
|
|
270
|
-
|
|
271
|
-
Minimum value for colormap normalization
|
|
272
|
-
|
|
273
|
-
Maximum value for colormap normalization
|
|
344
|
+
Figure size as (width, height) in inches.
|
|
345
|
+
clip_low : float | None
|
|
346
|
+
Minimum value for colormap normalization.
|
|
347
|
+
clip_high : float | None
|
|
348
|
+
Maximum value for colormap normalization.
|
|
274
349
|
ax : plt.Axes | None
|
|
275
350
|
Existing axes to plot on. If None, creates a new figure.
|
|
276
351
|
show_colorbar : bool
|
|
277
352
|
Whether to show a colorbar.
|
|
278
353
|
time_format : {"seconds", "ut"}
|
|
279
|
-
Format for the time axis.
|
|
280
|
-
|
|
354
|
+
Format for the time axis.
|
|
355
|
+
intensity_units : {"digits", "dB"}
|
|
356
|
+
Units for the intensity axis.
|
|
281
357
|
**imshow_kwargs
|
|
282
358
|
Additional keyword arguments passed to matplotlib's imshow().
|
|
283
|
-
Common options include:
|
|
284
|
-
- interpolation: str ("nearest", "bilinear", "bicubic", etc.)
|
|
285
|
-
- origin: str ("upper", "lower")
|
|
286
|
-
- alpha: float (transparency)
|
|
287
|
-
- norm: matplotlib.colors.Normalize (custom normalization)
|
|
288
359
|
|
|
289
360
|
Returns
|
|
290
361
|
-------
|
|
@@ -294,43 +365,22 @@ def plot_dynamic_spectrum(
|
|
|
294
365
|
Example
|
|
295
366
|
-------
|
|
296
367
|
>>> ds = read_fits("spectrum.fit.gz")
|
|
297
|
-
>>>
|
|
298
|
-
>>> fig, ax, im = plot_dynamic_spectrum(
|
|
299
|
-
... ds_reduced,
|
|
300
|
-
... title="Noise Reduced",
|
|
301
|
-
... vmin=-5, vmax=20,
|
|
302
|
-
... figsize=(12, 6),
|
|
303
|
-
... cmap="magma"
|
|
304
|
-
... )
|
|
368
|
+
>>> fig, ax, im = plot_raw_spectrum(ds, figsize=(12, 6), cmap="plasma")
|
|
305
369
|
"""
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
im = ax.imshow(
|
|
314
|
-
ds.data,
|
|
315
|
-
aspect="auto",
|
|
316
|
-
extent=extent,
|
|
370
|
+
return plot_dynamic_spectrum(
|
|
371
|
+
ds,
|
|
372
|
+
process="raw",
|
|
373
|
+
clip_low=clip_low,
|
|
374
|
+
clip_high=clip_high,
|
|
375
|
+
title=title,
|
|
317
376
|
cmap=cmap,
|
|
318
|
-
|
|
319
|
-
|
|
377
|
+
figsize=figsize,
|
|
378
|
+
ax=ax,
|
|
379
|
+
show_colorbar=show_colorbar,
|
|
380
|
+
time_format=time_format,
|
|
381
|
+
intensity_units=intensity_units,
|
|
320
382
|
**imshow_kwargs,
|
|
321
383
|
)
|
|
322
|
-
# Use filename-based title if not provided
|
|
323
|
-
if title is None:
|
|
324
|
-
title = _get_filename_title(ds, "dynamic_spectrum")
|
|
325
|
-
ax.set_title(title)
|
|
326
|
-
_format_time_axis(ax, converter, time_format)
|
|
327
|
-
ax.set_ylabel("Frequency [MHz]")
|
|
328
|
-
|
|
329
|
-
if show_colorbar:
|
|
330
|
-
cbar = fig.colorbar(im, ax=ax)
|
|
331
|
-
cbar.set_label("Intensity [DN]")
|
|
332
|
-
|
|
333
|
-
return fig, ax, im
|
|
334
384
|
|
|
335
385
|
|
|
336
386
|
def plot_background_subtracted(
|
|
@@ -338,34 +388,34 @@ def plot_background_subtracted(
|
|
|
338
388
|
title: str | None = None,
|
|
339
389
|
cmap: str = "jet",
|
|
340
390
|
figsize: tuple[float, float] | None = None,
|
|
341
|
-
|
|
342
|
-
|
|
391
|
+
clip_low: float | None = None,
|
|
392
|
+
clip_high: float | None = None,
|
|
343
393
|
ax: Optional[plt.Axes] = None,
|
|
344
394
|
show_colorbar: bool = True,
|
|
345
395
|
time_format: Literal["seconds", "ut"] = "seconds",
|
|
396
|
+
intensity_units: Literal["digits", "dB"] = "digits",
|
|
346
397
|
**imshow_kwargs,
|
|
347
398
|
) -> tuple["Figure", "Axes", "AxesImage"]:
|
|
348
399
|
"""
|
|
349
400
|
Plot a DynamicSpectrum after background subtraction (before clipping).
|
|
350
401
|
|
|
351
|
-
This is a convenience function that
|
|
352
|
-
|
|
353
|
-
the intermediate step before clipping is applied in noise reduction.
|
|
402
|
+
This is a convenience function that calls plot_dynamic_spectrum with
|
|
403
|
+
process="background_subtracted".
|
|
354
404
|
|
|
355
405
|
Parameters
|
|
356
406
|
----------
|
|
357
407
|
ds : DynamicSpectrum
|
|
358
408
|
The raw dynamic spectrum (will be background-subtracted internally).
|
|
359
|
-
title : str
|
|
409
|
+
title : str | None
|
|
360
410
|
Plot title.
|
|
361
411
|
cmap : str
|
|
362
|
-
Matplotlib colormap name. Default is "
|
|
363
|
-
|
|
412
|
+
Matplotlib colormap name. Default is "jet" which works well for
|
|
413
|
+
showing positive/negative deviations.
|
|
364
414
|
figsize : tuple[float, float] | None
|
|
365
415
|
Figure size as (width, height) in inches.
|
|
366
|
-
|
|
416
|
+
clip_low : float | None
|
|
367
417
|
Minimum value for colormap normalization.
|
|
368
|
-
|
|
418
|
+
clip_high : float | None
|
|
369
419
|
Maximum value for colormap normalization.
|
|
370
420
|
ax : plt.Axes | None
|
|
371
421
|
Existing axes to plot on. If None, creates a new figure.
|
|
@@ -373,6 +423,8 @@ def plot_background_subtracted(
|
|
|
373
423
|
Whether to show a colorbar.
|
|
374
424
|
time_format : {"seconds", "ut"}
|
|
375
425
|
Format for the time axis.
|
|
426
|
+
intensity_units : {"digits", "dB"}
|
|
427
|
+
Units for the intensity axis.
|
|
376
428
|
**imshow_kwargs
|
|
377
429
|
Additional keyword arguments passed to matplotlib's imshow().
|
|
378
430
|
|
|
@@ -384,23 +436,183 @@ def plot_background_subtracted(
|
|
|
384
436
|
Example
|
|
385
437
|
-------
|
|
386
438
|
>>> ds = read_fits("spectrum.fit.gz")
|
|
387
|
-
>>> fig, ax, im = plot_background_subtracted(ds,
|
|
439
|
+
>>> fig, ax, im = plot_background_subtracted(ds, clip_low=-10, clip_high=30)
|
|
388
440
|
"""
|
|
389
|
-
from .processing import background_subtract
|
|
390
|
-
|
|
391
|
-
ds_bg = background_subtract(ds)
|
|
392
|
-
# Use filename-based title if not provided
|
|
393
|
-
if title is None:
|
|
394
|
-
title = _get_filename_title(ds, "background_subtracted")
|
|
395
441
|
return plot_dynamic_spectrum(
|
|
396
|
-
|
|
442
|
+
ds,
|
|
443
|
+
process="background_subtracted",
|
|
444
|
+
clip_low=clip_low,
|
|
445
|
+
clip_high=clip_high,
|
|
397
446
|
title=title,
|
|
398
447
|
cmap=cmap,
|
|
399
448
|
figsize=figsize,
|
|
400
|
-
vmin=vmin,
|
|
401
|
-
vmax=vmax,
|
|
402
449
|
ax=ax,
|
|
403
450
|
show_colorbar=show_colorbar,
|
|
404
451
|
time_format=time_format,
|
|
452
|
+
intensity_units=intensity_units,
|
|
405
453
|
**imshow_kwargs,
|
|
406
454
|
)
|
|
455
|
+
|
|
456
|
+
|
|
457
|
+
def plot_light_curve(
|
|
458
|
+
ds: DynamicSpectrum,
|
|
459
|
+
frequency_mhz: float,
|
|
460
|
+
process: Literal["raw", "background_subtracted", "noise_reduced"] = "raw",
|
|
461
|
+
title: str | None = None,
|
|
462
|
+
figsize: tuple[float, float] | None = None,
|
|
463
|
+
ax: Optional[plt.Axes] = None,
|
|
464
|
+
time_format: Literal["seconds", "ut"] = "seconds",
|
|
465
|
+
clip_low: float | None = None,
|
|
466
|
+
clip_high: float | None = None,
|
|
467
|
+
intensity_units: Literal["digits", "dB"] = "digits",
|
|
468
|
+
**plot_kwargs,
|
|
469
|
+
) -> tuple["Figure", "Axes", "plt.Line2D"]:
|
|
470
|
+
"""
|
|
471
|
+
Plot a light curve (intensity vs time) at a specific frequency.
|
|
472
|
+
|
|
473
|
+
This function extracts the intensity values at the frequency channel closest
|
|
474
|
+
to the specified frequency and plots them against time. The data can be
|
|
475
|
+
plotted raw, after background subtraction, or after full noise reduction.
|
|
476
|
+
|
|
477
|
+
Parameters
|
|
478
|
+
----------
|
|
479
|
+
ds : DynamicSpectrum
|
|
480
|
+
The dynamic spectrum to extract the light curve from.
|
|
481
|
+
frequency_mhz : float
|
|
482
|
+
The target frequency in MHz. The function will use the closest
|
|
483
|
+
available frequency channel in the spectrum.
|
|
484
|
+
process : {"raw", "background_subtracted", "noise_reduced"}
|
|
485
|
+
Processing to apply before plotting:
|
|
486
|
+
- "raw": Use the original data without any processing.
|
|
487
|
+
- "background_subtracted": Subtract mean over time for each frequency.
|
|
488
|
+
- "noise_reduced": Apply full noise reduction (requires clip_low/clip_high).
|
|
489
|
+
title : str | None
|
|
490
|
+
Plot title. If None, generates title from filename and frequency.
|
|
491
|
+
figsize : tuple[float, float] | None
|
|
492
|
+
Figure size as (width, height) in inches. Ignored if ax is provided.
|
|
493
|
+
ax : plt.Axes | None
|
|
494
|
+
Existing axes to plot on. If None, creates a new figure.
|
|
495
|
+
time_format : {"seconds", "ut"}
|
|
496
|
+
Format for the time axis. "seconds" shows elapsed seconds,
|
|
497
|
+
"ut" shows Universal Time (requires ut_start_sec in metadata).
|
|
498
|
+
clip_low : float | None
|
|
499
|
+
Lower clipping threshold for noise reduction. Required if process="noise_reduced".
|
|
500
|
+
clip_high : float | None
|
|
501
|
+
Upper clipping threshold for noise reduction. Required if process="noise_reduced".
|
|
502
|
+
intensity_units : {"digits", "dB"}
|
|
503
|
+
Units for the intensity axis. "digits" shows raw ADU values,
|
|
504
|
+
"dB" converts using dB = Digits * 0.384 (pseudo-calibration).
|
|
505
|
+
**plot_kwargs
|
|
506
|
+
Additional keyword arguments passed to matplotlib's plot().
|
|
507
|
+
|
|
508
|
+
Returns
|
|
509
|
+
-------
|
|
510
|
+
tuple[Figure, Axes, Line2D]
|
|
511
|
+
The figure, axes, and line objects.
|
|
512
|
+
|
|
513
|
+
Raises
|
|
514
|
+
------
|
|
515
|
+
FrequencyOutOfRangeError
|
|
516
|
+
If the requested frequency is outside the spectrum's frequency range.
|
|
517
|
+
ValueError
|
|
518
|
+
If process="noise_reduced" but clip_low or clip_high is not provided.
|
|
519
|
+
|
|
520
|
+
Example
|
|
521
|
+
-------
|
|
522
|
+
>>> ds = read_fits("spectrum.fit.gz")
|
|
523
|
+
>>> # Plot raw light curve at 60 MHz
|
|
524
|
+
>>> fig, ax, line = plot_light_curve(ds, frequency_mhz=60, process="raw")
|
|
525
|
+
>>> # Plot noise-reduced light curve with custom clipping
|
|
526
|
+
>>> fig, ax, line = plot_light_curve(
|
|
527
|
+
... ds, frequency_mhz=60, process="noise_reduced",
|
|
528
|
+
... clip_low=-5, clip_high=20
|
|
529
|
+
... )
|
|
530
|
+
"""
|
|
531
|
+
from .exceptions import FrequencyOutOfRangeError
|
|
532
|
+
from .processing import background_subtract, noise_reduce_mean_clip
|
|
533
|
+
|
|
534
|
+
# Validate frequency is within range
|
|
535
|
+
freq_min = float(ds.freqs_mhz.min())
|
|
536
|
+
freq_max = float(ds.freqs_mhz.max())
|
|
537
|
+
|
|
538
|
+
if frequency_mhz < freq_min or frequency_mhz > freq_max:
|
|
539
|
+
raise FrequencyOutOfRangeError(
|
|
540
|
+
f"Requested frequency {frequency_mhz} MHz is outside the spectrum's "
|
|
541
|
+
f"frequency range [{freq_min:.2f}, {freq_max:.2f}] MHz."
|
|
542
|
+
)
|
|
543
|
+
|
|
544
|
+
# Validate clip parameters for noise_reduced
|
|
545
|
+
if process == "noise_reduced":
|
|
546
|
+
if clip_low is None or clip_high is None:
|
|
547
|
+
raise ValueError(
|
|
548
|
+
"When process='noise_reduced', both clip_low and clip_high must be provided."
|
|
549
|
+
)
|
|
550
|
+
|
|
551
|
+
# Find the closest frequency channel
|
|
552
|
+
freq_idx = int(np.argmin(np.abs(ds.freqs_mhz - frequency_mhz)))
|
|
553
|
+
actual_freq = float(ds.freqs_mhz[freq_idx])
|
|
554
|
+
|
|
555
|
+
# Apply processing
|
|
556
|
+
if process == "background_subtracted":
|
|
557
|
+
ds_processed = background_subtract(ds)
|
|
558
|
+
elif process == "noise_reduced":
|
|
559
|
+
ds_processed = noise_reduce_mean_clip(
|
|
560
|
+
ds, clip_low=clip_low, clip_high=clip_high, scale=None
|
|
561
|
+
)
|
|
562
|
+
else: # raw
|
|
563
|
+
ds_processed = ds
|
|
564
|
+
|
|
565
|
+
# Extract light curve data
|
|
566
|
+
light_curve = ds_processed.data[freq_idx, :]
|
|
567
|
+
|
|
568
|
+
# Convert to dB if requested
|
|
569
|
+
if intensity_units == "dB":
|
|
570
|
+
light_curve = light_curve * DIGITS_TO_DB_FACTOR
|
|
571
|
+
|
|
572
|
+
# Create figure and axes
|
|
573
|
+
if ax is None:
|
|
574
|
+
fig, ax = plt.subplots(figsize=figsize)
|
|
575
|
+
else:
|
|
576
|
+
fig = ax.figure
|
|
577
|
+
|
|
578
|
+
# Prepare time axis
|
|
579
|
+
if time_format == "ut":
|
|
580
|
+
converter = TimeAxisConverter.from_dynamic_spectrum(ds)
|
|
581
|
+
time_values = ds.time_s + converter.ut_start_sec
|
|
582
|
+
ax.set_xlabel("Time [UT]")
|
|
583
|
+
# Format x-tick labels as UT times
|
|
584
|
+
from matplotlib.ticker import FuncFormatter
|
|
585
|
+
|
|
586
|
+
def fmt(x, pos):
|
|
587
|
+
return converter.seconds_to_ut(x - converter.ut_start_sec)
|
|
588
|
+
|
|
589
|
+
ax.xaxis.set_major_formatter(FuncFormatter(fmt))
|
|
590
|
+
else:
|
|
591
|
+
time_values = ds.time_s
|
|
592
|
+
ax.set_xlabel("Time [s]")
|
|
593
|
+
|
|
594
|
+
# Plot the light curve
|
|
595
|
+
(line,) = ax.plot(time_values, light_curve, **plot_kwargs)
|
|
596
|
+
|
|
597
|
+
# Set title
|
|
598
|
+
if title is None:
|
|
599
|
+
# Map process name to title suffix
|
|
600
|
+
title_suffix = "noise_clipped" if process == "noise_reduced" else process
|
|
601
|
+
if ds.source is not None:
|
|
602
|
+
filename = ds.source.stem
|
|
603
|
+
# Strip .fit if it's a double extension like .fit.gz
|
|
604
|
+
if filename.endswith(".fit"):
|
|
605
|
+
filename = filename[:-4]
|
|
606
|
+
title = f"{filename}_light_curve_{actual_freq:.1f}MHz_{title_suffix}"
|
|
607
|
+
else:
|
|
608
|
+
title = f"Light Curve @ {actual_freq:.1f} MHz ({title_suffix})"
|
|
609
|
+
|
|
610
|
+
ax.set_title(title)
|
|
611
|
+
|
|
612
|
+
if intensity_units == "dB":
|
|
613
|
+
ax.set_ylabel("Intensity [dB]")
|
|
614
|
+
else:
|
|
615
|
+
ax.set_ylabel("Intensity [Digits]")
|
|
616
|
+
|
|
617
|
+
return fig, ax, line
|
|
618
|
+
|
ecallistolib/processing.py
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"""
|
|
2
2
|
e-callistolib: Tools for e-CALLISTO FITS dynamic spectra.
|
|
3
|
-
Version 0.2.
|
|
3
|
+
Version 0.2.3
|
|
4
4
|
Sahan S Liyanage (sahanslst@gmail.com)
|
|
5
5
|
Astronomical and Space Science Unit, University of Colombo, Sri Lanka.
|
|
6
6
|
"""
|
|
@@ -14,8 +14,8 @@ from .models import DynamicSpectrum
|
|
|
14
14
|
|
|
15
15
|
def noise_reduce_mean_clip(
|
|
16
16
|
ds: DynamicSpectrum,
|
|
17
|
-
clip_low: float
|
|
18
|
-
clip_high: float
|
|
17
|
+
clip_low: float,
|
|
18
|
+
clip_high: float,
|
|
19
19
|
scale: float | None = (2500.0 / 255.0 / 25.4),
|
|
20
20
|
) -> DynamicSpectrum:
|
|
21
21
|
"""
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: ecallistolib
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3
|
|
4
4
|
Summary: Tools to download, read, process, and plot e-CALLISTO FITS dynamic spectra.
|
|
5
5
|
Author: Sahan S. Liyanage
|
|
6
6
|
License: MIT
|
|
@@ -70,6 +70,20 @@ A Python library to **download**, **read**, **process**, and **plot** e-CALLISTO
|
|
|
70
70
|
|
|
71
71
|
## Installation
|
|
72
72
|
|
|
73
|
+
|
|
74
|
+
### From PyPI (Stable)
|
|
75
|
+
|
|
76
|
+
```bash
|
|
77
|
+
pip install ecallistolib
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
### Optional Dependencies
|
|
81
|
+
|
|
82
|
+
Install optional features as needed:
|
|
83
|
+
|
|
84
|
+
```bash
|
|
85
|
+
pip install ecallistolib"[download,plot]"
|
|
86
|
+
```
|
|
73
87
|
### From Source (Development)
|
|
74
88
|
|
|
75
89
|
```bash
|
|
@@ -103,11 +117,14 @@ import ecallistolib as ecl
|
|
|
103
117
|
# Read a FITS file
|
|
104
118
|
spectrum = ecl.read_fits("ALASKA_20230101_120000_01.fit.gz")
|
|
105
119
|
|
|
106
|
-
#
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
120
|
+
# Plot with different processing modes
|
|
121
|
+
fig, ax, im = ecl.plot_dynamic_spectrum(
|
|
122
|
+
spectrum,
|
|
123
|
+
process="noise_reduced",
|
|
124
|
+
clip_low=-5,
|
|
125
|
+
clip_high=20,
|
|
126
|
+
title="Solar Radio Burst"
|
|
127
|
+
)
|
|
111
128
|
```
|
|
112
129
|
|
|
113
130
|
---
|
|
@@ -186,14 +203,11 @@ import ecallistolib as ecl
|
|
|
186
203
|
|
|
187
204
|
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
188
205
|
|
|
189
|
-
# Apply noise reduction with
|
|
190
|
-
cleaned = ecl.noise_reduce_mean_clip(spectrum)
|
|
191
|
-
|
|
192
|
-
# Or customize the parameters
|
|
206
|
+
# Apply noise reduction with required clipping values
|
|
193
207
|
cleaned = ecl.noise_reduce_mean_clip(
|
|
194
208
|
spectrum,
|
|
195
|
-
clip_low=-5.0, # Lower clipping threshold
|
|
196
|
-
clip_high=20.0, # Upper clipping threshold
|
|
209
|
+
clip_low=-5.0, # Lower clipping threshold (required)
|
|
210
|
+
clip_high=20.0, # Upper clipping threshold (required)
|
|
197
211
|
scale=2500.0 / 255.0 / 25.4 # Scaling factor (None to disable)
|
|
198
212
|
)
|
|
199
213
|
|
|
@@ -240,7 +254,7 @@ spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
|
240
254
|
cropped = ecl.crop_frequency(spectrum, freq_min=100, freq_max=300)
|
|
241
255
|
|
|
242
256
|
# Crop to specific time range (in seconds)
|
|
243
|
-
cropped =
|
|
257
|
+
cropped = ecl.crop_time(spectrum, time_min=10, time_max=60)
|
|
244
258
|
|
|
245
259
|
# Crop both axes at once
|
|
246
260
|
cropped = ecl.crop(spectrum, freq_range=(100, 300), time_range=(10, 60))
|
|
@@ -319,28 +333,28 @@ if ecl.can_combine_time(files):
|
|
|
319
333
|
|
|
320
334
|
### Plotting
|
|
321
335
|
|
|
322
|
-
Create dynamic spectrum visualizations with
|
|
336
|
+
Create dynamic spectrum visualizations with selectable processing modes:
|
|
323
337
|
|
|
324
338
|
```python
|
|
325
339
|
import ecallistolib as ecl
|
|
326
340
|
import matplotlib.pyplot as plt
|
|
327
341
|
|
|
328
|
-
spectrum =
|
|
329
|
-
cleaned = ecf.noise_reduce_mean_clip(spectrum)
|
|
342
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
330
343
|
|
|
331
|
-
#
|
|
332
|
-
fig, ax, im =
|
|
344
|
+
# Plot raw spectrum
|
|
345
|
+
fig, ax, im = ecl.plot_dynamic_spectrum(spectrum, process="raw")
|
|
333
346
|
plt.show()
|
|
334
347
|
|
|
335
|
-
#
|
|
336
|
-
fig, ax, im =
|
|
337
|
-
|
|
348
|
+
# Plot noise-reduced spectrum with required clipping values
|
|
349
|
+
fig, ax, im = ecl.plot_dynamic_spectrum(
|
|
350
|
+
spectrum,
|
|
351
|
+
process="noise_reduced", # Apply noise reduction
|
|
352
|
+
clip_low=-5, # Lower clipping bound (required)
|
|
353
|
+
clip_high=20, # Upper clipping bound (required)
|
|
338
354
|
title="Type III Solar Burst",
|
|
339
|
-
cmap="magma",
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
figsize=(12, 6), # Figure size in inches
|
|
343
|
-
interpolation="bilinear" # Any matplotlib imshow kwarg
|
|
355
|
+
cmap="magma",
|
|
356
|
+
figsize=(12, 6),
|
|
357
|
+
interpolation="bilinear"
|
|
344
358
|
)
|
|
345
359
|
plt.savefig("spectrum.png", dpi=150, bbox_inches="tight")
|
|
346
360
|
```
|
|
@@ -348,12 +362,12 @@ plt.savefig("spectrum.png", dpi=150, bbox_inches="tight")
|
|
|
348
362
|
#### Plotting Raw Data
|
|
349
363
|
|
|
350
364
|
```python
|
|
351
|
-
import ecallistolib as
|
|
365
|
+
import ecallistolib as ecl
|
|
352
366
|
|
|
353
|
-
spectrum =
|
|
367
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
354
368
|
|
|
355
369
|
# Plot raw spectrum without any processing
|
|
356
|
-
fig, ax, im =
|
|
370
|
+
fig, ax, im = ecl.plot_raw_spectrum(
|
|
357
371
|
spectrum,
|
|
358
372
|
title="Raw Spectrum",
|
|
359
373
|
cmap="viridis",
|
|
@@ -364,15 +378,15 @@ fig, ax, im = ecf.plot_raw_spectrum(
|
|
|
364
378
|
#### Plotting Background Subtracted (Before Clipping)
|
|
365
379
|
|
|
366
380
|
```python
|
|
367
|
-
import ecallistolib as
|
|
381
|
+
import ecallistolib as ecl
|
|
368
382
|
|
|
369
|
-
spectrum =
|
|
383
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
370
384
|
|
|
371
385
|
# Plot after background subtraction but before clipping
|
|
372
|
-
fig, ax, im =
|
|
386
|
+
fig, ax, im = ecl.plot_background_subtracted(
|
|
373
387
|
spectrum,
|
|
374
|
-
|
|
375
|
-
|
|
388
|
+
clip_low=-10,
|
|
389
|
+
clip_high=30,
|
|
376
390
|
cmap="RdBu_r" # Diverging colormap for +/- values
|
|
377
391
|
)
|
|
378
392
|
```
|
|
@@ -382,28 +396,46 @@ fig, ax, im = ecf.plot_background_subtracted(
|
|
|
382
396
|
Display time in seconds or Universal Time (UT):
|
|
383
397
|
|
|
384
398
|
```python
|
|
385
|
-
import ecallistolib as
|
|
399
|
+
import ecallistolib as ecl
|
|
386
400
|
|
|
387
|
-
spectrum =
|
|
401
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
388
402
|
|
|
389
403
|
# Default: time in seconds
|
|
390
|
-
|
|
404
|
+
ecl.plot_dynamic_spectrum(spectrum, time_format="seconds")
|
|
391
405
|
|
|
392
406
|
# Time in UT format (HH:MM:SS)
|
|
393
|
-
|
|
407
|
+
ecl.plot_dynamic_spectrum(spectrum, time_format="ut")
|
|
408
|
+
```
|
|
409
|
+
|
|
410
|
+
#### Intensity Units
|
|
411
|
+
|
|
412
|
+
Choose between raw digital values (Digits/ADU) or pseudo-calibrated dB:
|
|
413
|
+
|
|
414
|
+
```python
|
|
415
|
+
import ecallistolib as ecl
|
|
416
|
+
|
|
417
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
418
|
+
|
|
419
|
+
# Default: intensity in Digits (raw ADU values)
|
|
420
|
+
ecl.plot_dynamic_spectrum(spectrum, intensity_units="digits")
|
|
421
|
+
|
|
422
|
+
# Convert to dB using: dB = Digits * 0.384 (pseudo-calibration)
|
|
423
|
+
ecl.plot_dynamic_spectrum(spectrum, intensity_units="dB")
|
|
394
424
|
```
|
|
395
425
|
|
|
426
|
+
> **Note:** The dB conversion uses the formula: dB = Digits × 2500 / 256 / 25.4 ≈ Digits × 0.384
|
|
427
|
+
|
|
396
428
|
#### Time Axis Converter
|
|
397
429
|
|
|
398
430
|
Convert between elapsed seconds and UT time programmatically:
|
|
399
431
|
|
|
400
432
|
```python
|
|
401
|
-
import ecallistolib as
|
|
433
|
+
import ecallistolib as ecl
|
|
402
434
|
|
|
403
|
-
spectrum =
|
|
435
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
404
436
|
|
|
405
437
|
# Create converter from spectrum metadata
|
|
406
|
-
converter =
|
|
438
|
+
converter = ecl.TimeAxisConverter.from_dynamic_spectrum(spectrum)
|
|
407
439
|
|
|
408
440
|
# Convert seconds to UT
|
|
409
441
|
print(converter.seconds_to_ut(100)) # "12:01:40"
|
|
@@ -418,19 +450,72 @@ print(converter.ut_to_seconds("13:00:00")) # 3600.0
|
|
|
418
450
|
|
|
419
451
|
```python
|
|
420
452
|
import matplotlib.pyplot as plt
|
|
421
|
-
import ecallistolib as
|
|
453
|
+
import ecallistolib as ecl
|
|
422
454
|
|
|
423
455
|
fig, axes = plt.subplots(1, 2, figsize=(14, 5))
|
|
424
456
|
|
|
425
|
-
spectrum1 =
|
|
426
|
-
spectrum2 =
|
|
457
|
+
spectrum1 = ecl.read_fits("file1.fit.gz")
|
|
458
|
+
spectrum2 = ecl.read_fits("file2.fit.gz")
|
|
427
459
|
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
460
|
+
ecl.plot_dynamic_spectrum(spectrum1, process="raw", ax=axes[0], title="Raw")
|
|
461
|
+
ecl.plot_dynamic_spectrum(
|
|
462
|
+
spectrum2,
|
|
463
|
+
process="noise_reduced",
|
|
431
464
|
ax=axes[1],
|
|
432
465
|
title="Noise Reduced",
|
|
433
|
-
|
|
466
|
+
clip_low=-5, clip_high=20
|
|
467
|
+
)
|
|
468
|
+
|
|
469
|
+
plt.tight_layout()
|
|
470
|
+
plt.show()
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
#### Light Curve Plotting
|
|
474
|
+
|
|
475
|
+
Plot intensity vs time at a specific frequency:
|
|
476
|
+
|
|
477
|
+
```python
|
|
478
|
+
import ecallistolib as ecl
|
|
479
|
+
import matplotlib.pyplot as plt
|
|
480
|
+
|
|
481
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
482
|
+
|
|
483
|
+
# Plot raw light curve at 60 MHz
|
|
484
|
+
fig, ax, line = ecl.plot_light_curve(spectrum, frequency_mhz=60, process="raw")
|
|
485
|
+
plt.show()
|
|
486
|
+
|
|
487
|
+
# Plot background-subtracted light curve
|
|
488
|
+
fig, ax, line = ecl.plot_light_curve(
|
|
489
|
+
spectrum, frequency_mhz=60, process="background_subtracted"
|
|
490
|
+
)
|
|
491
|
+
plt.show()
|
|
492
|
+
|
|
493
|
+
# Plot noise-reduced light curve (must provide clip values)
|
|
494
|
+
fig, ax, line = ecl.plot_light_curve(
|
|
495
|
+
spectrum,
|
|
496
|
+
frequency_mhz=60,
|
|
497
|
+
process="noise_reduced",
|
|
498
|
+
clip_low=-5,
|
|
499
|
+
clip_high=20
|
|
500
|
+
)
|
|
501
|
+
plt.show()
|
|
502
|
+
```
|
|
503
|
+
|
|
504
|
+
Compare all three processing modes:
|
|
505
|
+
|
|
506
|
+
```python
|
|
507
|
+
import ecallistolib as ecl
|
|
508
|
+
import matplotlib.pyplot as plt
|
|
509
|
+
|
|
510
|
+
spectrum = ecl.read_fits("my_spectrum.fit.gz")
|
|
511
|
+
|
|
512
|
+
fig, axes = plt.subplots(3, 1, figsize=(12, 10))
|
|
513
|
+
|
|
514
|
+
ecl.plot_light_curve(spectrum, 60, process="raw", ax=axes[0], title="Raw")
|
|
515
|
+
ecl.plot_light_curve(spectrum, 60, process="background_subtracted", ax=axes[1], title="BG Sub")
|
|
516
|
+
ecl.plot_light_curve(
|
|
517
|
+
spectrum, 60, process="noise_reduced", ax=axes[2], title="Noise Reduced",
|
|
518
|
+
clip_low=-5, clip_high=20
|
|
434
519
|
)
|
|
435
520
|
|
|
436
521
|
plt.tight_layout()
|
|
@@ -638,36 +723,65 @@ Concatenate spectra horizontally (time concatenation).
|
|
|
638
723
|
|
|
639
724
|
### Plotting Functions
|
|
640
725
|
|
|
641
|
-
#### `plot_dynamic_spectrum(ds,
|
|
726
|
+
#### `plot_dynamic_spectrum(ds, process="raw", clip_low=None, clip_high=None, title=None, cmap="inferno", figsize=None, ax=None, show_colorbar=True, time_format="seconds", intensity_units="digits", **imshow_kwargs)`
|
|
642
727
|
|
|
643
|
-
Plot a dynamic spectrum with
|
|
728
|
+
Plot a dynamic spectrum with selectable processing mode.
|
|
644
729
|
|
|
645
730
|
| Parameter | Type | Default | Description |
|
|
646
731
|
|-----------|------|---------|-------------|
|
|
647
732
|
| `ds` | `DynamicSpectrum` | — | Spectrum to plot |
|
|
648
|
-
| `
|
|
733
|
+
| `process` | `str` | `"raw"` | Processing mode: `"raw"`, `"background_subtracted"`, or `"noise_reduced"` |
|
|
734
|
+
| `clip_low` | `float \| None` | `None` | Lower clipping bound (required for `"noise_reduced"`) |
|
|
735
|
+
| `clip_high` | `float \| None` | `None` | Upper clipping bound (required for `"noise_reduced"`) |
|
|
736
|
+
| `title` | `str \| None` | `None` | Plot title (auto-generated if `None`) |
|
|
649
737
|
| `cmap` | `str` | `"inferno"` | Matplotlib colormap |
|
|
650
738
|
| `figsize` | `tuple \| None` | `None` | Figure size as `(width, height)` in inches |
|
|
651
|
-
| `vmin` | `float \| None` | `None` | Colormap lower bound (clipping) |
|
|
652
|
-
| `vmax` | `float \| None` | `None` | Colormap upper bound (clipping) |
|
|
653
739
|
| `ax` | `Axes \| None` | `None` | Existing axes (creates new if `None`) |
|
|
654
740
|
| `show_colorbar` | `bool` | `True` | Whether to display colorbar |
|
|
655
741
|
| `time_format` | `str` | `"seconds"` | `"seconds"` or `"ut"` for time axis format |
|
|
742
|
+
| `intensity_units` | `str` | `"digits"` | `"digits"` (raw ADU) or `"dB"` (pseudo-calibrated) |
|
|
656
743
|
| `**imshow_kwargs` | — | — | Additional kwargs passed to `matplotlib.imshow()` |
|
|
657
744
|
|
|
658
745
|
**Returns:** Tuple of `(fig, ax, im)`.
|
|
659
746
|
|
|
747
|
+
**Raises:** `ValueError` if `process="noise_reduced"` without `clip_low` and `clip_high`.
|
|
748
|
+
|
|
660
749
|
---
|
|
661
750
|
|
|
662
|
-
#### `plot_raw_spectrum(ds,
|
|
751
|
+
#### `plot_raw_spectrum(ds, clip_low=None, clip_high=None, title=None, cmap="viridis", ...)`
|
|
663
752
|
|
|
664
|
-
|
|
753
|
+
Convenience function that calls `plot_dynamic_spectrum` with `process="raw"`.
|
|
665
754
|
|
|
666
755
|
---
|
|
667
756
|
|
|
668
|
-
#### `plot_background_subtracted(ds,
|
|
757
|
+
#### `plot_background_subtracted(ds, clip_low=None, clip_high=None, title=None, cmap="jet", ...)`
|
|
758
|
+
|
|
759
|
+
Convenience function that calls `plot_dynamic_spectrum` with `process="background_subtracted"`.
|
|
760
|
+
|
|
761
|
+
---
|
|
762
|
+
|
|
763
|
+
#### `plot_light_curve(ds, frequency_mhz, process="raw", title=None, figsize=None, ax=None, time_format="seconds", clip_low=None, clip_high=None, **plot_kwargs)`
|
|
764
|
+
|
|
765
|
+
Plot a light curve (intensity vs time) at a specific frequency.
|
|
669
766
|
|
|
670
|
-
|
|
767
|
+
| Parameter | Type | Default | Description |
|
|
768
|
+
|-----------|------|---------|-------------|
|
|
769
|
+
| `ds` | `DynamicSpectrum` | — | Spectrum to extract light curve from |
|
|
770
|
+
| `frequency_mhz` | `float` | — | Target frequency in MHz |
|
|
771
|
+
| `process` | `str` | `"raw"` | Processing mode: `"raw"`, `"background_subtracted"`, or `"noise_reduced"` |
|
|
772
|
+
| `title` | `str \| None` | `None` | Plot title (auto-generated if `None`) |
|
|
773
|
+
| `figsize` | `tuple \| None` | `None` | Figure size as `(width, height)` in inches |
|
|
774
|
+
| `ax` | `Axes \| None` | `None` | Existing axes (creates new if `None`) |
|
|
775
|
+
| `time_format` | `str` | `"seconds"` | `"seconds"` or `"ut"` for time axis format |
|
|
776
|
+
| `clip_low` | `float \| None` | `None` | Lower clip threshold (required for `"noise_reduced"`) |
|
|
777
|
+
| `clip_high` | `float \| None` | `None` | Upper clip threshold (required for `"noise_reduced"`) |
|
|
778
|
+
| `**plot_kwargs` | — | — | Additional kwargs passed to `matplotlib.plot()` |
|
|
779
|
+
|
|
780
|
+
**Returns:** Tuple of `(fig, ax, line)`.
|
|
781
|
+
|
|
782
|
+
**Raises:**
|
|
783
|
+
- `FrequencyOutOfRangeError` if frequency is outside spectrum's range.
|
|
784
|
+
- `ValueError` if `process="noise_reduced"` without `clip_low` and `clip_high`.
|
|
671
785
|
|
|
672
786
|
---
|
|
673
787
|
|
|
@@ -701,22 +815,23 @@ The library provides a hierarchy of custom exceptions for robust error handling:
|
|
|
701
815
|
| `DownloadError` | Raised when downloading files from the archive fails |
|
|
702
816
|
| `CombineError` | Raised when spectra cannot be combined |
|
|
703
817
|
| `CropError` | Raised when cropping parameters are invalid |
|
|
818
|
+
| `FrequencyOutOfRangeError` | Raised when the requested frequency is outside the spectrum's range |
|
|
704
819
|
|
|
705
820
|
#### Error Handling Example
|
|
706
821
|
|
|
707
822
|
```python
|
|
708
|
-
import ecallistolib as
|
|
823
|
+
import ecallistolib as ecl
|
|
709
824
|
from ecallistolib import InvalidFITSError, CropError
|
|
710
825
|
|
|
711
826
|
try:
|
|
712
|
-
spectrum =
|
|
827
|
+
spectrum = ecl.read_fits("corrupted_file.fit")
|
|
713
828
|
except FileNotFoundError:
|
|
714
829
|
print("File not found")
|
|
715
830
|
except InvalidFITSError as e:
|
|
716
831
|
print(f"Invalid FITS file: {e}")
|
|
717
832
|
|
|
718
833
|
try:
|
|
719
|
-
cropped =
|
|
834
|
+
cropped = ecl.crop(spectrum, freq_range=(1000, 2000)) # Out of range
|
|
720
835
|
except CropError as e:
|
|
721
836
|
print(f"Cropping failed: {e}")
|
|
722
837
|
```
|
|
@@ -729,24 +844,24 @@ except CropError as e:
|
|
|
729
844
|
|
|
730
845
|
```python
|
|
731
846
|
from datetime import date
|
|
732
|
-
import ecallistolib as
|
|
847
|
+
import ecallistolib as ecl
|
|
733
848
|
import matplotlib.pyplot as plt
|
|
734
849
|
|
|
735
850
|
# 1. Download data
|
|
736
|
-
remote =
|
|
737
|
-
paths =
|
|
851
|
+
remote = ecl.list_remote_fits(date(2023, 6, 15), hour=12, station_substring="alaska")
|
|
852
|
+
paths = ecl.download_files(remote[:2], out_dir="./data")
|
|
738
853
|
|
|
739
854
|
# 2. Read and combine
|
|
740
|
-
if
|
|
741
|
-
spectrum =
|
|
855
|
+
if ecl.can_combine_time(paths):
|
|
856
|
+
spectrum = ecl.combine_time(paths)
|
|
742
857
|
else:
|
|
743
|
-
spectrum =
|
|
858
|
+
spectrum = ecl.read_fits(paths[0])
|
|
744
859
|
|
|
745
860
|
# 3. Process
|
|
746
|
-
cleaned =
|
|
861
|
+
cleaned = ecl.noise_reduce_mean_clip(spectrum)
|
|
747
862
|
|
|
748
863
|
# 4. Plot
|
|
749
|
-
fig, ax, im =
|
|
864
|
+
fig, ax, im = ecl.plot_dynamic_spectrum(
|
|
750
865
|
cleaned,
|
|
751
866
|
title=f"e-CALLISTO Observation - {spectrum.meta.get('station', 'Unknown')}",
|
|
752
867
|
cmap="plasma"
|
|
@@ -758,9 +873,9 @@ plt.show()
|
|
|
758
873
|
### Working with Metadata
|
|
759
874
|
|
|
760
875
|
```python
|
|
761
|
-
import ecallistolib as
|
|
876
|
+
import ecallistolib as ecl
|
|
762
877
|
|
|
763
|
-
spectrum =
|
|
878
|
+
spectrum = ecl.read_fits("my_file.fit.gz")
|
|
764
879
|
|
|
765
880
|
# Access metadata
|
|
766
881
|
print(f"Station: {spectrum.meta.get('station')}")
|
|
@@ -768,7 +883,7 @@ print(f"Date: {spectrum.meta.get('date')}")
|
|
|
768
883
|
print(f"UT Start: {spectrum.meta.get('ut_start_sec')} seconds")
|
|
769
884
|
|
|
770
885
|
# After processing, metadata is preserved and extended
|
|
771
|
-
processed =
|
|
886
|
+
processed = ecl.noise_reduce_mean_clip(spectrum)
|
|
772
887
|
print(f"Processing applied: {processed.meta.get('noise_reduction')}")
|
|
773
888
|
```
|
|
774
889
|
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
ecallistolib/__init__.py,sha256=MOEjuYi132aHYdFNN8ReRcu95eXOVWFsyRvX2zPb6Os,3003
|
|
2
|
+
ecallistolib/combine.py,sha256=wYDh1pzh4DMZTLD2KRQOCtRbtt2y-PC8xmQvC5xl1LA,3315
|
|
3
|
+
ecallistolib/crop.py,sha256=DactJZ9DtZ50zj7Cp6Ii0F6sCieVloGyzWqMY7HmJ9I,6383
|
|
4
|
+
ecallistolib/download.py,sha256=XbqcLqptKS16D8MP9bWZZprFviQdw1hbt9ctsW1N91s,4114
|
|
5
|
+
ecallistolib/exceptions.py,sha256=e993ColPiVOyOP7Dh7RY4GlRoClwFCPABLiWaS5cLuk,1027
|
|
6
|
+
ecallistolib/io.py,sha256=_uSw6L6Bm1t5GjpVMGtdoF_5NmgEVVDzCXW2nRVFbV8,3686
|
|
7
|
+
ecallistolib/models.py,sha256=Mv8fRWKsbNlMQi20_6oxUz9UX6wkckZAcS9Tj0kH-b4,1299
|
|
8
|
+
ecallistolib/plotting.py,sha256=rEItUrxAXfMj76_MPmoycgfxcbsuWztc3cduWKppkSU,20616
|
|
9
|
+
ecallistolib/processing.py,sha256=enwu8KSome09u4Iv5zjhY80t1HbnGFz3xXf2Wr3Lp7k,1962
|
|
10
|
+
ecallistolib-0.2.3.dist-info/licenses/LICENSE,sha256=WunjkzsBGyy9vIQxfNe_GDV1yKBQJ-0WbFt4AXZ5Rvc,1073
|
|
11
|
+
ecallistolib-0.2.3.dist-info/METADATA,sha256=vqzLuqmPGXxIKwF2HABGorRI6YusKD15p_XAQ3kQEmg,26290
|
|
12
|
+
ecallistolib-0.2.3.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
|
|
13
|
+
ecallistolib-0.2.3.dist-info/top_level.txt,sha256=DmLjR5jlE2i2mQXou5gyCpaHOOlNs4DIQyCPoGXhsbc,13
|
|
14
|
+
ecallistolib-0.2.3.dist-info/RECORD,,
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
ecallistolib/__init__.py,sha256=9O277WBX99gg3MqWcv-tiaQxB6IKeMfHM91uDJP_9O0,2833
|
|
2
|
-
ecallistolib/combine.py,sha256=79LuR2HngpTbaM2xBg7aqUGK_1xkK3RomKmomWG2FpE,3315
|
|
3
|
-
ecallistolib/crop.py,sha256=zcAsd5XP-_lG9exBPUFYt4PJslzxLabjSLvbdSADhDk,6383
|
|
4
|
-
ecallistolib/download.py,sha256=gUmDLZDjyVIElzqZnGoAREkpOaxYNsAUDeujlGwd_o4,4114
|
|
5
|
-
ecallistolib/exceptions.py,sha256=F0EdRCi54ut8qQDtDVZxhGn3P_JbsoQ8hNVxKCFpO5s,879
|
|
6
|
-
ecallistolib/io.py,sha256=e-BMtWvILsqs2ua89Ysa2vwZ9s4mDukDGVPYDA3ed-E,3686
|
|
7
|
-
ecallistolib/models.py,sha256=v8bl1fN3jKHVbQmpfVSv3ZusFrkWZJ1ciZkuufygmjE,1299
|
|
8
|
-
ecallistolib/plotting.py,sha256=RhTJK5qGwCcBRRJP-mDNyGoWzd5diHCbx1IyXK8PraU,12296
|
|
9
|
-
ecallistolib/processing.py,sha256=eBH3uGt3RmGM1NRdH_wJ7AAcVeadk9te-pHc9pWy2Lo,1976
|
|
10
|
-
ecallistolib-0.2.1.dist-info/licenses/LICENSE,sha256=WunjkzsBGyy9vIQxfNe_GDV1yKBQJ-0WbFt4AXZ5Rvc,1073
|
|
11
|
-
ecallistolib-0.2.1.dist-info/METADATA,sha256=LPysk03p7kqiyr311C2R-CKwYWnq_yVgyK4n8eLSlMM,22583
|
|
12
|
-
ecallistolib-0.2.1.dist-info/WHEEL,sha256=qELbo2s1Yzl39ZmrAibXA2jjPLUYfnVhUNTlyF1rq0Y,92
|
|
13
|
-
ecallistolib-0.2.1.dist-info/top_level.txt,sha256=DmLjR5jlE2i2mQXou5gyCpaHOOlNs4DIQyCPoGXhsbc,13
|
|
14
|
-
ecallistolib-0.2.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|