ecallistolib 0.2.3.1__py3-none-any.whl → 0.3.0__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 CHANGED
@@ -20,7 +20,7 @@ from .exceptions import (
20
20
  )
21
21
  from .io import CallistoFileParts, parse_callisto_filename, read_fits
22
22
  from .models import DynamicSpectrum
23
- from .processing import noise_reduce_mean_clip
23
+ from .processing import noise_reduce_mean_clip, noise_reduce_median_clip
24
24
  from .crop import crop, crop_frequency, crop_time, slice_by_index
25
25
 
26
26
  try:
@@ -39,6 +39,7 @@ __all__ = [
39
39
  "read_fits",
40
40
  # Processing
41
41
  "noise_reduce_mean_clip",
42
+ "noise_reduce_median_clip",
42
43
  # Cropping
43
44
  "crop",
44
45
  "crop_frequency",
@@ -77,12 +78,14 @@ def __getattr__(name: str):
77
78
  "combine_time": combine_time,
78
79
  }[name]
79
80
 
80
- if name in {"list_remote_fits", "download_files"}:
81
- from .download import download_files, list_remote_fits
81
+ if name in {"list_remote_fits", "list_remote_fits_range", "download_files"}:
82
+ from .download import download_files, list_remote_fits, list_remote_fits_range
82
83
 
83
- return {"list_remote_fits": list_remote_fits, "download_files": download_files}[
84
- name
85
- ]
84
+ return {
85
+ "list_remote_fits": list_remote_fits,
86
+ "list_remote_fits_range": list_remote_fits_range,
87
+ "download_files": download_files,
88
+ }[name]
86
89
 
87
90
  if name in {
88
91
  "plot_dynamic_spectrum",
ecallistolib/download.py CHANGED
@@ -99,6 +99,81 @@ def list_remote_fits(
99
99
  return out
100
100
 
101
101
 
102
+ def list_remote_fits_range(
103
+ start_date: date,
104
+ end_date: date,
105
+ hours: Iterable[int] | None = None,
106
+ station_substring: str = "",
107
+ base_url: str = DEFAULT_BASE_URL,
108
+ timeout_s: float = 10.0,
109
+ ) -> List[RemoteFITS]:
110
+ """
111
+ List available FITS files over a date range.
112
+
113
+ Parameters
114
+ ----------
115
+ start_date : date
116
+ Start date (inclusive).
117
+ end_date : date
118
+ End date (inclusive).
119
+ hours : Iterable[int] | None
120
+ UTC hours to include (0-23). If None, includes all hours (0-23).
121
+ station_substring : str
122
+ Case-insensitive substring to match station names.
123
+ base_url : str
124
+ Base URL of the e-CALLISTO archive.
125
+ timeout_s : float
126
+ HTTP request timeout in seconds.
127
+
128
+ Returns
129
+ -------
130
+ List[RemoteFITS]
131
+ All matching remote FITS files across the date range.
132
+
133
+ Raises
134
+ ------
135
+ ValueError
136
+ If start_date > end_date.
137
+
138
+ Example
139
+ -------
140
+ >>> from datetime import date
141
+ >>> files = list_remote_fits_range(
142
+ ... start_date=date(2023, 6, 1),
143
+ ... end_date=date(2023, 6, 3),
144
+ ... hours=[12, 13, 14],
145
+ ... station_substring="alaska"
146
+ ... )
147
+ """
148
+ from datetime import timedelta
149
+
150
+ if start_date > end_date:
151
+ raise ValueError("start_date must be <= end_date")
152
+
153
+ hours_to_check = list(hours) if hours is not None else list(range(24))
154
+
155
+ results: List[RemoteFITS] = []
156
+ current = start_date
157
+
158
+ while current <= end_date:
159
+ for hour in hours_to_check:
160
+ try:
161
+ found = list_remote_fits(
162
+ day=current,
163
+ hour=hour,
164
+ station_substring=station_substring,
165
+ base_url=base_url,
166
+ timeout_s=timeout_s,
167
+ )
168
+ results.extend(found)
169
+ except DownloadError:
170
+ # Skip days/hours with no data or connection issues
171
+ continue
172
+ current += timedelta(days=1)
173
+
174
+ return results
175
+
176
+
102
177
  def download_files(
103
178
  items: Iterable[RemoteFITS],
104
179
  out_dir: str | Path,
ecallistolib/models.py CHANGED
@@ -42,3 +42,23 @@ class DynamicSpectrum:
42
42
  @property
43
43
  def shape(self) -> tuple[int, int]:
44
44
  return int(self.data.shape[0]), int(self.data.shape[1])
45
+
46
+ @property
47
+ def n_freq(self) -> int:
48
+ """Number of frequency channels."""
49
+ return self.data.shape[0]
50
+
51
+ @property
52
+ def n_time(self) -> int:
53
+ """Number of time samples."""
54
+ return self.data.shape[1]
55
+
56
+ @property
57
+ def duration_s(self) -> float:
58
+ """Total observation duration in seconds."""
59
+ return float(self.time_s[-1] - self.time_s[0])
60
+
61
+ @property
62
+ def freq_range_mhz(self) -> tuple[float, float]:
63
+ """Frequency range as (min, max) in MHz."""
64
+ return float(self.freqs_mhz.min()), float(self.freqs_mhz.max())
@@ -68,3 +68,54 @@ def background_subtract(ds: DynamicSpectrum) -> DynamicSpectrum:
68
68
  meta = dict(ds.meta)
69
69
  meta["processing"] = {"method": "background_subtract"}
70
70
  return ds.copy_with(data=data, meta=meta)
71
+
72
+
73
+ def noise_reduce_median_clip(
74
+ ds: DynamicSpectrum,
75
+ clip_low: float,
76
+ clip_high: float,
77
+ scale: float | None = (2500.0 / 255.0 / 25.4),
78
+ ) -> DynamicSpectrum:
79
+ """
80
+ Noise reduction using median subtraction and clipping.
81
+
82
+ More robust to outliers than mean-based subtraction. Uses the same
83
+ algorithm as noise_reduce_mean_clip but substitutes the median for
84
+ the mean when computing the background level.
85
+
86
+ Algorithm:
87
+ 1) Subtract median over time for each frequency channel
88
+ 2) Clip to [clip_low, clip_high]
89
+ 3) Apply optional scaling
90
+
91
+ Parameters
92
+ ----------
93
+ ds : DynamicSpectrum
94
+ Input dynamic spectrum.
95
+ clip_low : float
96
+ Lower clipping threshold.
97
+ clip_high : float
98
+ Upper clipping threshold.
99
+ scale : float | None
100
+ Scaling factor. Default is ~3.88 (2500/255/25.4).
101
+ Set to None to disable scaling.
102
+
103
+ Returns
104
+ -------
105
+ DynamicSpectrum
106
+ New spectrum with noise reduced.
107
+ """
108
+ data = np.array(ds.data, copy=True, dtype=float)
109
+ data = data - np.median(data, axis=1, keepdims=True)
110
+ data = np.clip(data, clip_low, clip_high)
111
+ if scale is not None:
112
+ data = data * float(scale)
113
+
114
+ meta = dict(ds.meta)
115
+ meta["noise_reduction"] = {
116
+ "method": "median_subtract_clip",
117
+ "clip_low": clip_low,
118
+ "clip_high": clip_high,
119
+ "scale": scale,
120
+ }
121
+ return ds.copy_with(data=data, meta=meta)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: ecallistolib
3
- Version: 0.2.3.1
3
+ Version: 0.3.0
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
@@ -29,6 +29,14 @@ A Python library to **download**, **read**, **process**, and **plot** e-CALLISTO
29
29
 
30
30
  ---
31
31
 
32
+ ## 🆕 What's New in v0.3.0
33
+
34
+ - **`DynamicSpectrum` convenience properties** — `n_freq`, `n_time`, `duration_s`, `freq_range_mhz` for quick data inspection
35
+ - **`list_remote_fits_range()`** — Query the e-CALLISTO archive across multiple days with optional hour filtering
36
+ - **`noise_reduce_median_clip()`** — Median-based noise reduction, more robust to outliers than mean-based method
37
+
38
+ ---
39
+
32
40
  ## Table of Contents
33
41
 
34
42
  - [Features](#features)
@@ -147,6 +155,12 @@ print(f"Frequencies: {spectrum.freqs_mhz}") # Frequency axis in MHz
147
155
  print(f"Time samples: {spectrum.time_s}") # Time axis in seconds
148
156
  print(f"Source file: {spectrum.source}") # Original file path
149
157
  print(f"Metadata: {spectrum.meta}") # Station, date, etc.
158
+
159
+ # New in v0.3.0: Convenience properties
160
+ print(f"Num frequencies: {spectrum.n_freq}") # Number of frequency channels
161
+ print(f"Num time samples: {spectrum.n_time}") # Number of time samples
162
+ print(f"Duration: {spectrum.duration_s} s") # Total observation duration
163
+ print(f"Freq range: {spectrum.freq_range_mhz}") # (min, max) frequency in MHz
150
164
  ```
151
165
 
152
166
  #### Parsing Filenames
@@ -190,6 +204,25 @@ for path in saved_paths:
190
204
  print(f"Downloaded: {path}")
191
205
  ```
192
206
 
207
+ #### Querying Multiple Days
208
+
209
+ List files over a date range with `list_remote_fits_range` (new in v0.3.0):
210
+
211
+ ```python
212
+ from datetime import date
213
+ import ecallistolib as ecl
214
+
215
+ # List files from June 1-3, 2023, during hours 12-14 UTC
216
+ remote_files = ecl.list_remote_fits_range(
217
+ start_date=date(2023, 6, 1),
218
+ end_date=date(2023, 6, 3),
219
+ hours=[12, 13, 14], # Optional: specific UTC hours
220
+ station_substring="alaska"
221
+ )
222
+
223
+ print(f"Found {len(remote_files)} files across 3 days")
224
+ ```
225
+
193
226
  ---
194
227
 
195
228
  ### Processing Data
@@ -237,6 +270,26 @@ bg_subtracted = ecl.background_subtract(spectrum)
237
270
  # Each frequency channel now has zero mean
238
271
  ```
239
272
 
273
+ #### Median-Based Noise Reduction (v0.3.0)
274
+
275
+ For data with outliers, use median-based subtraction which is more robust:
276
+
277
+ ```python
278
+ import ecallistolib as ecl
279
+
280
+ spectrum = ecl.read_fits("my_spectrum.fit.gz")
281
+
282
+ # Use median instead of mean (more robust to outliers)
283
+ cleaned = ecl.noise_reduce_median_clip(
284
+ spectrum,
285
+ clip_low=-5.0,
286
+ clip_high=20.0
287
+ )
288
+
289
+ # Metadata shows the method used
290
+ print(cleaned.meta["noise_reduction"]["method"]) # 'median_subtract_clip'
291
+ ```
292
+
240
293
  ---
241
294
 
242
295
  ### Cropping & Slicing
@@ -545,6 +598,10 @@ class DynamicSpectrum:
545
598
  | Property | Type | Description |
546
599
  |----------|------|-------------|
547
600
  | `shape` | `tuple[int, int]` | Returns `(n_freq, n_time)` |
601
+ | `n_freq` | `int` | Number of frequency channels |
602
+ | `n_time` | `int` | Number of time samples |
603
+ | `duration_s` | `float` | Total observation duration in seconds |
604
+ | `freq_range_mhz` | `tuple[float, float]` | Frequency range as `(min, max)` in MHz |
548
605
 
549
606
  #### Methods
550
607
 
@@ -612,6 +669,21 @@ Download FITS files to a local directory.
612
669
 
613
670
  ---
614
671
 
672
+ #### `list_remote_fits_range(start_date, end_date, hours=None, station_substring="", ...) -> List[RemoteFITS]`
673
+
674
+ List available FITS files over a date range (new in v0.3.0).
675
+
676
+ | Parameter | Type | Description |
677
+ |-----------|------|--------------|
678
+ | `start_date` | `date` | Start date (inclusive) |
679
+ | `end_date` | `date` | End date (inclusive) |
680
+ | `hours` | `Iterable[int] \| None` | UTC hours to include (0–23), or None for all |
681
+ | `station_substring` | `str` | Case-insensitive station filter |
682
+
683
+ **Returns:** List of `RemoteFITS` objects across the date range.
684
+
685
+ ---
686
+
615
687
  ### Processing Functions
616
688
 
617
689
  #### `noise_reduce_mean_clip(ds, clip_low=-5.0, clip_high=20.0, scale=...) -> DynamicSpectrum`
@@ -641,6 +713,21 @@ Subtract mean over time for each frequency channel (background subtraction only,
641
713
 
642
714
  ---
643
715
 
716
+ #### `noise_reduce_median_clip(ds, clip_low, clip_high, scale=...) -> DynamicSpectrum`
717
+
718
+ Apply noise reduction via median subtraction and clipping (new in v0.3.0). More robust to outliers than mean-based method.
719
+
720
+ | Parameter | Type | Default | Description |
721
+ |-----------|------|---------|-------------|
722
+ | `ds` | `DynamicSpectrum` | — | Input spectrum |
723
+ | `clip_low` | `float` | — | Lower clipping threshold |
724
+ | `clip_high` | `float` | — | Upper clipping threshold |
725
+ | `scale` | `float \| None` | `~3.88` | Scaling factor (`None` to disable) |
726
+
727
+ **Returns:** New `DynamicSpectrum` with processed data and updated metadata.
728
+
729
+ ---
730
+
644
731
  ### Cropping Functions
645
732
 
646
733
  #### `crop_frequency(ds, freq_min=None, freq_max=None) -> DynamicSpectrum`
@@ -0,0 +1,14 @@
1
+ ecallistolib/__init__.py,sha256=ufhvqfgjgW4oymwZJF8TsycDXjgCBbjOSG4VXFBY7BI,3186
2
+ ecallistolib/combine.py,sha256=G1ySitPAFbKVNL3u_-Fva4prdYXzq4glIpf8YC8aF5g,3327
3
+ ecallistolib/crop.py,sha256=DactJZ9DtZ50zj7Cp6Ii0F6sCieVloGyzWqMY7HmJ9I,6383
4
+ ecallistolib/download.py,sha256=cT1wWvpOUrbbsX-H4IxC_KiebTS-6L5VywHd2LxPVeo,6138
5
+ ecallistolib/exceptions.py,sha256=e993ColPiVOyOP7Dh7RY4GlRoClwFCPABLiWaS5cLuk,1027
6
+ ecallistolib/io.py,sha256=_uSw6L6Bm1t5GjpVMGtdoF_5NmgEVVDzCXW2nRVFbV8,3686
7
+ ecallistolib/models.py,sha256=XyPqIo-yDfuRHY2p6MkXovHkmQUV2ltSaGKXzoXzzDA,1887
8
+ ecallistolib/plotting.py,sha256=rEItUrxAXfMj76_MPmoycgfxcbsuWztc3cduWKppkSU,20616
9
+ ecallistolib/processing.py,sha256=0Flsj9wQfu17oZtxkC4A2c90HhU5aMp3iVWhVGKl_Mg,3409
10
+ ecallistolib-0.3.0.dist-info/licenses/LICENSE,sha256=WunjkzsBGyy9vIQxfNe_GDV1yKBQJ-0WbFt4AXZ5Rvc,1073
11
+ ecallistolib-0.3.0.dist-info/METADATA,sha256=dbplIiwUva-SL_Dd3_21uZnKWVgusOMOC2h0M5vQWu8,29447
12
+ ecallistolib-0.3.0.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
+ ecallistolib-0.3.0.dist-info/top_level.txt,sha256=DmLjR5jlE2i2mQXou5gyCpaHOOlNs4DIQyCPoGXhsbc,13
14
+ ecallistolib-0.3.0.dist-info/RECORD,,
@@ -1,14 +0,0 @@
1
- ecallistolib/__init__.py,sha256=MOEjuYi132aHYdFNN8ReRcu95eXOVWFsyRvX2zPb6Os,3003
2
- ecallistolib/combine.py,sha256=G1ySitPAFbKVNL3u_-Fva4prdYXzq4glIpf8YC8aF5g,3327
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.1.dist-info/licenses/LICENSE,sha256=WunjkzsBGyy9vIQxfNe_GDV1yKBQJ-0WbFt4AXZ5Rvc,1073
11
- ecallistolib-0.2.3.1.dist-info/METADATA,sha256=DKCz6dCWETaMp-LGv3nzlJmeurCal8zfljHeK8uc0YQ,26292
12
- ecallistolib-0.2.3.1.dist-info/WHEEL,sha256=wUyA8OaulRlbfwMtmQsvNngGrxQHAvkKcvRmdizlJi0,92
13
- ecallistolib-0.2.3.1.dist-info/top_level.txt,sha256=DmLjR5jlE2i2mQXou5gyCpaHOOlNs4DIQyCPoGXhsbc,13
14
- ecallistolib-0.2.3.1.dist-info/RECORD,,