solarviewer 1.0.2__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.
Files changed (82) hide show
  1. solar_radio_image_viewer/__init__.py +12 -0
  2. solar_radio_image_viewer/assets/add_tab_default.png +0 -0
  3. solar_radio_image_viewer/assets/add_tab_default_light.png +0 -0
  4. solar_radio_image_viewer/assets/add_tab_hover.png +0 -0
  5. solar_radio_image_viewer/assets/add_tab_hover_light.png +0 -0
  6. solar_radio_image_viewer/assets/browse.png +0 -0
  7. solar_radio_image_viewer/assets/browse_light.png +0 -0
  8. solar_radio_image_viewer/assets/close_tab_default.png +0 -0
  9. solar_radio_image_viewer/assets/close_tab_default_light.png +0 -0
  10. solar_radio_image_viewer/assets/close_tab_hover.png +0 -0
  11. solar_radio_image_viewer/assets/close_tab_hover_light.png +0 -0
  12. solar_radio_image_viewer/assets/ellipse_selection.png +0 -0
  13. solar_radio_image_viewer/assets/ellipse_selection_light.png +0 -0
  14. solar_radio_image_viewer/assets/icons8-ellipse-90.png +0 -0
  15. solar_radio_image_viewer/assets/icons8-ellipse-90_light.png +0 -0
  16. solar_radio_image_viewer/assets/icons8-info-90.png +0 -0
  17. solar_radio_image_viewer/assets/icons8-info-90_light.png +0 -0
  18. solar_radio_image_viewer/assets/profile.png +0 -0
  19. solar_radio_image_viewer/assets/profile_light.png +0 -0
  20. solar_radio_image_viewer/assets/rectangle_selection.png +0 -0
  21. solar_radio_image_viewer/assets/rectangle_selection_light.png +0 -0
  22. solar_radio_image_viewer/assets/reset.png +0 -0
  23. solar_radio_image_viewer/assets/reset_light.png +0 -0
  24. solar_radio_image_viewer/assets/ruler.png +0 -0
  25. solar_radio_image_viewer/assets/ruler_light.png +0 -0
  26. solar_radio_image_viewer/assets/search.png +0 -0
  27. solar_radio_image_viewer/assets/search_light.png +0 -0
  28. solar_radio_image_viewer/assets/settings.png +0 -0
  29. solar_radio_image_viewer/assets/settings_light.png +0 -0
  30. solar_radio_image_viewer/assets/splash.fits +0 -0
  31. solar_radio_image_viewer/assets/zoom_60arcmin.png +0 -0
  32. solar_radio_image_viewer/assets/zoom_60arcmin_light.png +0 -0
  33. solar_radio_image_viewer/assets/zoom_in.png +0 -0
  34. solar_radio_image_viewer/assets/zoom_in_light.png +0 -0
  35. solar_radio_image_viewer/assets/zoom_out.png +0 -0
  36. solar_radio_image_viewer/assets/zoom_out_light.png +0 -0
  37. solar_radio_image_viewer/create_video.py +1345 -0
  38. solar_radio_image_viewer/dialogs.py +2665 -0
  39. solar_radio_image_viewer/from_simpl/__init__.py +184 -0
  40. solar_radio_image_viewer/from_simpl/caltable_visualizer.py +1001 -0
  41. solar_radio_image_viewer/from_simpl/dynamic_spectra_dialog.py +332 -0
  42. solar_radio_image_viewer/from_simpl/make_dynamic_spectra.py +351 -0
  43. solar_radio_image_viewer/from_simpl/pipeline_logger_gui.py +1232 -0
  44. solar_radio_image_viewer/from_simpl/simpl_theme.py +352 -0
  45. solar_radio_image_viewer/from_simpl/utils.py +984 -0
  46. solar_radio_image_viewer/from_simpl/view_dynamic_spectra_GUI.py +1975 -0
  47. solar_radio_image_viewer/helioprojective.py +1916 -0
  48. solar_radio_image_viewer/helioprojective_viewer.py +817 -0
  49. solar_radio_image_viewer/helioviewer_browser.py +1514 -0
  50. solar_radio_image_viewer/main.py +148 -0
  51. solar_radio_image_viewer/move_phasecenter.py +1269 -0
  52. solar_radio_image_viewer/napari_viewer.py +368 -0
  53. solar_radio_image_viewer/noaa_events/__init__.py +32 -0
  54. solar_radio_image_viewer/noaa_events/noaa_events.py +430 -0
  55. solar_radio_image_viewer/noaa_events/noaa_events_gui.py +1922 -0
  56. solar_radio_image_viewer/norms.py +293 -0
  57. solar_radio_image_viewer/radio_data_downloader/__init__.py +25 -0
  58. solar_radio_image_viewer/radio_data_downloader/radio_data_downloader.py +756 -0
  59. solar_radio_image_viewer/radio_data_downloader/radio_data_downloader_gui.py +528 -0
  60. solar_radio_image_viewer/searchable_combobox.py +220 -0
  61. solar_radio_image_viewer/solar_context/__init__.py +41 -0
  62. solar_radio_image_viewer/solar_context/active_regions.py +371 -0
  63. solar_radio_image_viewer/solar_context/cme_alerts.py +234 -0
  64. solar_radio_image_viewer/solar_context/context_images.py +297 -0
  65. solar_radio_image_viewer/solar_context/realtime_data.py +528 -0
  66. solar_radio_image_viewer/solar_data_downloader/__init__.py +35 -0
  67. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader.py +1667 -0
  68. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader_cli.py +901 -0
  69. solar_radio_image_viewer/solar_data_downloader/solar_data_downloader_gui.py +1210 -0
  70. solar_radio_image_viewer/styles.py +643 -0
  71. solar_radio_image_viewer/utils/__init__.py +32 -0
  72. solar_radio_image_viewer/utils/rate_limiter.py +255 -0
  73. solar_radio_image_viewer/utils.py +952 -0
  74. solar_radio_image_viewer/video_dialog.py +2629 -0
  75. solar_radio_image_viewer/video_utils.py +656 -0
  76. solar_radio_image_viewer/viewer.py +11174 -0
  77. solarviewer-1.0.2.dist-info/METADATA +343 -0
  78. solarviewer-1.0.2.dist-info/RECORD +82 -0
  79. solarviewer-1.0.2.dist-info/WHEEL +5 -0
  80. solarviewer-1.0.2.dist-info/entry_points.txt +8 -0
  81. solarviewer-1.0.2.dist-info/licenses/LICENSE +21 -0
  82. solarviewer-1.0.2.dist-info/top_level.txt +1 -0
@@ -0,0 +1,293 @@
1
+ import numpy as np
2
+ from matplotlib.colors import Normalize
3
+ import warnings
4
+
5
+
6
+ class SqrtNorm(Normalize):
7
+ """
8
+ Square root normalization for enhancing faint features.
9
+
10
+ Applies sqrt(x) scaling which compresses bright values and enhances faint ones.
11
+ Commonly used in solar EUV imaging.
12
+ """
13
+ def __call__(self, value, clip=None):
14
+ normed = super().__call__(value, clip)
15
+ # Clip to avoid sqrt of negative values (can happen with edge cases)
16
+ normed = np.clip(normed, 0, 1)
17
+ # Handle masked arrays
18
+ if hasattr(normed, 'mask'):
19
+ result = np.ma.sqrt(normed)
20
+ else:
21
+ result = np.sqrt(normed)
22
+ return result
23
+
24
+
25
+ class AsinhNorm(Normalize):
26
+ """
27
+ Inverse hyperbolic sine (arcsinh) normalization.
28
+
29
+ Similar to logarithmic scaling but handles zero and negative values gracefully.
30
+ Excellent for high dynamic range solar images like AIA EUV channels.
31
+
32
+ Parameters
33
+ ----------
34
+ linear_width : float, optional
35
+ Controls the transition between linear and logarithmic behavior.
36
+ Smaller values = more compression of bright features. Default is 1e-3.
37
+ """
38
+ def __init__(self, vmin=None, vmax=None, clip=False, linear_width=1e-3):
39
+ super().__init__(vmin, vmax, clip)
40
+ # Validate linear_width
41
+ if linear_width <= 0:
42
+ warnings.warn("linear_width must be positive, using default 1e-3")
43
+ linear_width = 1e-3
44
+ self.linear_width = linear_width
45
+
46
+ def __call__(self, value, clip=None):
47
+ normed = super().__call__(value, clip)
48
+ # Protect against division by zero
49
+ with np.errstate(invalid='ignore', divide='ignore'):
50
+ denominator = np.arcsinh(1.0 / self.linear_width)
51
+ if denominator == 0:
52
+ return normed # Fall back to linear
53
+ result = np.arcsinh(normed / self.linear_width) / denominator
54
+ # Handle any NaN that might result
55
+ if hasattr(result, 'mask'):
56
+ return result
57
+ return np.nan_to_num(result, nan=0.0, posinf=1.0, neginf=0.0)
58
+
59
+
60
+ class PowerNorm(Normalize):
61
+ """
62
+ Power-law (gamma) normalization.
63
+
64
+ Applies x^gamma scaling. Gamma < 1 enhances faint features,
65
+ gamma > 1 enhances bright features.
66
+
67
+ Parameters
68
+ ----------
69
+ gamma : float, optional
70
+ The power exponent. Default is 1.0 (linear).
71
+ Common values: 0.5 (sqrt-like), 0.3 (strong faint enhancement)
72
+ """
73
+ def __init__(self, vmin=None, vmax=None, clip=False, gamma=1.0):
74
+ super().__init__(vmin, vmax, clip)
75
+ # Validate gamma
76
+ if gamma <= 0:
77
+ warnings.warn("gamma must be positive, using default 1.0")
78
+ gamma = 1.0
79
+ self.gamma = gamma
80
+
81
+ def __call__(self, value, clip=None):
82
+ normed = super().__call__(value, clip)
83
+ # Clip to ensure non-negative values for power operation
84
+ normed = np.clip(normed, 0, 1)
85
+ # Use safe power operation
86
+ with np.errstate(invalid='ignore'):
87
+ result = np.power(normed, self.gamma)
88
+ return np.nan_to_num(result, nan=0.0, posinf=1.0, neginf=0.0)
89
+
90
+
91
+ class ZScaleNorm(Normalize):
92
+ """
93
+ ZScale normalization using iterative sigma clipping (IRAF algorithm).
94
+
95
+ Automatically determines optimal display range by fitting a line to
96
+ sorted pixel values and rejecting outliers. Widely used in astronomy.
97
+
98
+ Parameters
99
+ ----------
100
+ contrast : float, optional
101
+ Contrast parameter that adjusts the slope (default is 0.25).
102
+ num_samples : int, optional
103
+ Number of samples to use if the image is large (default is 600).
104
+ max_iterations : int, optional
105
+ Maximum iterations for sigma clipping (default is 5).
106
+ krej : float, optional
107
+ Rejection threshold in units of sigma (default is 2.5).
108
+ min_npixels : int, optional
109
+ Minimum number of pixels required to continue fitting (default is 5).
110
+ """
111
+
112
+ def __init__(
113
+ self,
114
+ vmin=None,
115
+ vmax=None,
116
+ clip=False,
117
+ contrast=0.25,
118
+ num_samples=600,
119
+ max_iterations=5,
120
+ krej=2.5,
121
+ min_npixels=5,
122
+ ):
123
+ super().__init__(vmin, vmax, clip)
124
+ # Validate parameters
125
+ self.contrast = max(0.01, contrast) # Prevent division by zero
126
+ self.num_samples = max(10, num_samples)
127
+ self.max_iterations = max(1, max_iterations)
128
+ self.krej = max(0.5, krej)
129
+ self.min_npixels = max(2, min_npixels)
130
+ self._zscale_computed = False
131
+ self._zmin = None
132
+ self._zmax = None
133
+
134
+ def _compute_zscale(self, data):
135
+ """Compute ZScale limits using iterative sigma clipping."""
136
+ try:
137
+ # Flatten data and remove NaNs/Infs
138
+ flat_data = np.asarray(data).flatten()
139
+ mask = np.isfinite(flat_data)
140
+ flat_data = flat_data[mask]
141
+
142
+ if flat_data.size == 0:
143
+ return 0, 1
144
+
145
+ if flat_data.size < self.min_npixels:
146
+ return float(np.min(flat_data)), float(np.max(flat_data))
147
+
148
+ # Sample the data if necessary
149
+ if flat_data.size > self.num_samples:
150
+ indices = np.linspace(0, flat_data.size - 1, self.num_samples).astype(int)
151
+ samples = np.sort(flat_data[indices])
152
+ else:
153
+ samples = np.sort(flat_data)
154
+
155
+ # Iterative sigma clipping
156
+ for _ in range(self.max_iterations):
157
+ if len(samples) < self.min_npixels:
158
+ break
159
+
160
+ x = np.arange(len(samples))
161
+ try:
162
+ slope, intercept = np.polyfit(x, samples, 1)
163
+ except (np.linalg.LinAlgError, ValueError):
164
+ break
165
+
166
+ fitted = slope * x + intercept
167
+ residuals = samples - fitted
168
+ sigma = np.std(residuals)
169
+
170
+ if sigma == 0:
171
+ break
172
+
173
+ mask = np.abs(residuals) < self.krej * sigma
174
+ if np.sum(mask) < self.min_npixels:
175
+ break
176
+
177
+ new_samples = samples[mask]
178
+ if new_samples.size == samples.size:
179
+ break
180
+ samples = new_samples
181
+
182
+ # Adjust slope by contrast parameter
183
+ if slope != 0:
184
+ slope /= self.contrast
185
+
186
+ # Use median as pivot
187
+ median_index = len(samples) // 2
188
+ median_val = samples[median_index]
189
+
190
+ # Compute zscale limits
191
+ zmin = median_val - slope * median_index
192
+ zmax = median_val + slope * (len(samples) - median_index)
193
+
194
+ # Ensure limits are within data range
195
+ zmin = max(zmin, samples[0])
196
+ zmax = min(zmax, samples[-1])
197
+
198
+ # Ensure zmin < zmax
199
+ if zmin >= zmax:
200
+ zmin = samples[0]
201
+ zmax = samples[-1]
202
+
203
+ return float(zmin), float(zmax)
204
+
205
+ except Exception as e:
206
+ warnings.warn(f"ZScale computation failed: {e}, using min/max")
207
+ flat = np.asarray(data).flatten()
208
+ flat = flat[np.isfinite(flat)]
209
+ if flat.size > 0:
210
+ return float(np.min(flat)), float(np.max(flat))
211
+ return 0, 1
212
+
213
+ def __call__(self, value, clip=None):
214
+ if not self._zscale_computed and isinstance(value, np.ndarray):
215
+ self._zmin, self._zmax = self._compute_zscale(value)
216
+ self._zscale_computed = True
217
+ self.vmin = self._zmin
218
+ self.vmax = self._zmax
219
+ return super().__call__(value, clip)
220
+
221
+
222
+ class HistEqNorm(Normalize):
223
+ """
224
+ Histogram equalization normalization.
225
+
226
+ Enhances contrast by redistributing intensity values so that the
227
+ cumulative distribution function becomes approximately linear.
228
+ Useful for images with poor contrast or non-uniform intensity distribution.
229
+
230
+ Parameters
231
+ ----------
232
+ n_bins : int, optional
233
+ Number of bins for the histogram (default is 256).
234
+ """
235
+
236
+ def __init__(self, vmin=None, vmax=None, clip=False, n_bins=256):
237
+ super().__init__(vmin, vmax, clip)
238
+ self.n_bins = max(16, min(4096, n_bins)) # Clamp to reasonable range
239
+ self._hist_eq_computed = False
240
+ self._hist_eq_map = None
241
+
242
+ def _compute_hist_eq(self, data):
243
+ """Compute histogram equalization mapping."""
244
+ try:
245
+ flat_data = np.asarray(data).flatten()
246
+ flat_data = flat_data[np.isfinite(flat_data)]
247
+
248
+ if flat_data.size == 0:
249
+ return np.linspace(0, 1, self.n_bins), np.linspace(0, 1, self.n_bins)
250
+
251
+ # Compute histogram and CDF
252
+ hist, bin_edges = np.histogram(flat_data, bins=self.n_bins)
253
+
254
+ # Avoid division by zero
255
+ hist_sum = hist.sum()
256
+ if hist_sum == 0:
257
+ return np.linspace(0, 1, self.n_bins), np.linspace(0, 1, self.n_bins)
258
+
259
+ cdf = hist.cumsum() / hist_sum
260
+ bin_centers = (bin_edges[:-1] + bin_edges[1:]) / 2
261
+
262
+ return bin_centers, cdf
263
+
264
+ except Exception as e:
265
+ warnings.warn(f"Histogram equalization failed: {e}")
266
+ return np.linspace(0, 1, self.n_bins), np.linspace(0, 1, self.n_bins)
267
+
268
+ def __call__(self, value, clip=None):
269
+ try:
270
+ if not self._hist_eq_computed and isinstance(value, np.ndarray):
271
+ normed = super().__call__(value, clip)
272
+ bin_centers, cdf = self._compute_hist_eq(normed)
273
+ self._hist_eq_map = (bin_centers, cdf)
274
+ self._hist_eq_computed = True
275
+ return self._apply_hist_eq(normed, bin_centers, cdf)
276
+ elif self._hist_eq_computed and self._hist_eq_map is not None:
277
+ normed = super().__call__(value, clip)
278
+ bin_centers, cdf = self._hist_eq_map
279
+ return self._apply_hist_eq(normed, bin_centers, cdf)
280
+ else:
281
+ return super().__call__(value, clip)
282
+ except Exception:
283
+ return super().__call__(value, clip)
284
+
285
+ def _apply_hist_eq(self, normed, bin_centers, cdf):
286
+ """Apply histogram equalization mapping."""
287
+ if np.isscalar(normed):
288
+ idx = np.abs(bin_centers - normed).argmin()
289
+ return cdf[idx]
290
+ else:
291
+ normed_flat = np.asarray(normed).flatten()
292
+ result = np.interp(normed_flat, bin_centers, cdf)
293
+ return result.reshape(normed.shape)
@@ -0,0 +1,25 @@
1
+ """
2
+ Radio Data Downloader Package
3
+
4
+ This package provides tools for downloading and processing radio solar data from various observatories:
5
+ - Learmonth Solar Observatory (Australia)
6
+
7
+ The package includes:
8
+ - Core downloader module (radio_data_downloader.py)
9
+ - Graphical user interface (radio_data_downloader_gui.py)
10
+ """
11
+
12
+ from .radio_data_downloader import (
13
+ download_learmonth,
14
+ srs_to_dataframe,
15
+ dataframe_to_fits,
16
+ )
17
+
18
+ from .radio_data_downloader_gui import launch_gui
19
+
20
+ __all__ = [
21
+ "download_learmonth",
22
+ "srs_to_dataframe",
23
+ "dataframe_to_fits",
24
+ "launch_gui",
25
+ ]