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,368 @@
1
+ #!/usr/bin/env python3
2
+ """
3
+ Basic napari viewer for Fits/CASA images.
4
+ """
5
+
6
+ import os
7
+ import sys
8
+ import numpy as np
9
+ import napari
10
+ from PyQt5.QtWidgets import (
11
+ QApplication,
12
+ QFileDialog,
13
+ QPushButton,
14
+ QVBoxLayout,
15
+ QHBoxLayout,
16
+ QWidget,
17
+ QLabel,
18
+ QComboBox,
19
+ QRadioButton,
20
+ QButtonGroup,
21
+ QMessageBox,
22
+ QGroupBox,
23
+ QLineEdit,
24
+ )
25
+ from PyQt5.QtCore import Qt
26
+ from .utils import get_pixel_values_from_image
27
+
28
+
29
+ class NapariViewer(QWidget):
30
+ def __init__(self, imagename=None):
31
+ super().__init__()
32
+ self.viewer = None
33
+ self.current_image_data = None
34
+ self.current_wcs = None
35
+ self.psf = None
36
+ self.image_layer = None
37
+ self.imagename = imagename
38
+
39
+ self.init_ui()
40
+
41
+ # If an image was provided, load it
42
+ if self.imagename:
43
+ self.load_data(self.imagename)
44
+ self.plot_image()
45
+
46
+ def init_ui(self):
47
+ self.setWindowTitle("Napari Image Viewer")
48
+ self.resize(1200, 800)
49
+
50
+ # Main layout
51
+ main_layout = QHBoxLayout()
52
+ self.setLayout(main_layout)
53
+
54
+ # Left column for controls
55
+ left_column = QVBoxLayout()
56
+ main_layout.addLayout(left_column, 1)
57
+
58
+ # Right column for napari viewer
59
+ right_column = QVBoxLayout()
60
+ main_layout.addLayout(right_column, 3)
61
+
62
+ # File selection group
63
+ file_group = QGroupBox("File Selection")
64
+ file_layout = QVBoxLayout()
65
+
66
+ # Add help button
67
+ help_layout = QHBoxLayout()
68
+ help_button = QPushButton("Help")
69
+ help_button.clicked.connect(self.show_help)
70
+ help_layout.addStretch()
71
+ help_layout.addWidget(help_button)
72
+ left_column.addLayout(help_layout)
73
+
74
+ # Radio buttons for file type selection
75
+ radio_layout = QVBoxLayout() # Changed to vertical layout for better spacing
76
+ self.radio_fits = QRadioButton("FITS File")
77
+ self.radio_casa_image = QRadioButton("CASA Image")
78
+ self.radio_fits.setChecked(True)
79
+
80
+ self.file_type_group = QButtonGroup()
81
+ self.file_type_group.addButton(self.radio_fits)
82
+ self.file_type_group.addButton(self.radio_casa_image)
83
+
84
+ radio_layout.addWidget(self.radio_fits)
85
+ radio_layout.addWidget(self.radio_casa_image)
86
+ file_layout.addLayout(radio_layout)
87
+
88
+ # File selection button
89
+ self.file_button = QPushButton("Open Image")
90
+ self.file_button.setMinimumHeight(40) # Make button taller
91
+ self.file_button.clicked.connect(self.select_file_or_directory)
92
+ file_layout.addWidget(self.file_button)
93
+
94
+ # Add a spacer for better layout
95
+ file_layout.addStretch()
96
+
97
+ file_group.setLayout(file_layout)
98
+ left_column.addWidget(file_group)
99
+
100
+ # Add current file display
101
+ file_info_group = QGroupBox("Current File")
102
+ file_info_layout = QVBoxLayout()
103
+ self.file_label = QLabel("No file loaded")
104
+ self.file_label.setWordWrap(True)
105
+ file_info_layout.addWidget(self.file_label)
106
+ file_info_group.setLayout(file_info_layout)
107
+ left_column.addWidget(file_info_group)
108
+
109
+ # Add a spacer at the bottom of the left column
110
+ left_column.addStretch()
111
+
112
+ # Display controls group
113
+ display_group = QGroupBox("Display Controls")
114
+ display_layout = QVBoxLayout()
115
+
116
+ # Stokes parameter selection
117
+ stokes_layout = QHBoxLayout()
118
+ self.stokes_label = QLabel("Stokes:")
119
+ stokes_layout.addWidget(self.stokes_label)
120
+
121
+ self.stokes_combo = QComboBox()
122
+ self.stokes_combo.addItems(
123
+ ["I", "Q", "U", "V", "L", "Lfrac", "Vfrac", "Q/I", "U/I", "U/V"]
124
+ )
125
+ self.stokes_combo.currentTextChanged.connect(self.on_stokes_changed)
126
+ stokes_layout.addWidget(self.stokes_combo)
127
+ display_layout.addLayout(stokes_layout)
128
+
129
+ # Add threshold textbox
130
+ threshold_layout = QHBoxLayout()
131
+ self.threshold_label = QLabel("Threshold:")
132
+ threshold_layout.addWidget(self.threshold_label)
133
+ self.threshold_textbox = QLineEdit("10.0")
134
+ threshold_layout.addWidget(self.threshold_textbox)
135
+ self.threshold_textbox.editingFinished.connect(
136
+ lambda: self.on_threshold_changed(self.threshold_textbox.text())
137
+ )
138
+ display_layout.addLayout(threshold_layout)
139
+
140
+ # Add a spacer for better layout
141
+ display_layout.addStretch()
142
+
143
+ display_group.setLayout(display_layout)
144
+ right_column.addWidget(display_group)
145
+
146
+ # Image statistics group
147
+ stats_group = QGroupBox("Image Statistics")
148
+ stats_layout = QVBoxLayout()
149
+
150
+ self.stats_label = QLabel("No image loaded")
151
+ stats_layout.addWidget(self.stats_label)
152
+
153
+ stats_group.setLayout(stats_layout)
154
+ right_column.addWidget(stats_group)
155
+
156
+ # Add a spacer at the bottom of the right column
157
+ right_column.addStretch()
158
+
159
+ # Initialize napari viewer
160
+ self.init_napari()
161
+
162
+ def init_napari(self):
163
+ """Initialize the napari viewer"""
164
+ self.viewer = napari.Viewer(show=False)
165
+ self.viewer.window.add_dock_widget(self, area="bottom")
166
+ self.viewer.show()
167
+
168
+ def select_file_or_directory(self):
169
+ """Open a file dialog to select a FITS file or CASA image directory"""
170
+ if self.radio_casa_image.isChecked():
171
+ # Select CASA image directory
172
+ directory = QFileDialog.getExistingDirectory(
173
+ self, "Select a CASA Image Directory"
174
+ )
175
+ if directory:
176
+ try:
177
+ self.load_data(directory)
178
+ self.plot_image()
179
+ except Exception as e:
180
+ QMessageBox.critical(
181
+ self, "Error", f"Failed to load CASA image: {str(e)}"
182
+ )
183
+ else:
184
+ # Select FITS file
185
+ file_path, _ = QFileDialog.getOpenFileName(
186
+ self, "Select a FITS file", "", "FITS files (*.fits);;All files (*)"
187
+ )
188
+ if file_path:
189
+ try:
190
+ self.load_data(file_path)
191
+ self.plot_image()
192
+ except Exception as e:
193
+ QMessageBox.critical(
194
+ self, "Error", f"Failed to load FITS file: {str(e)}"
195
+ )
196
+
197
+ def load_data(self, imagename, threshold=10):
198
+ """Load data from a FITS file or CASA image directory"""
199
+ stokes = self.stokes_combo.currentText()
200
+
201
+ try:
202
+ pix, csys, psf = get_pixel_values_from_image(imagename, stokes, threshold)
203
+ pix = pix.transpose()
204
+ pix = np.flip(pix, axis=0)
205
+
206
+ self.current_image_data = pix
207
+ self.current_wcs = csys
208
+ self.psf = psf
209
+ self.imagename = imagename # Store the imagename for later use
210
+
211
+ # Update file label
212
+ self.file_label.setText(
213
+ f"File: {os.path.basename(imagename)}\nType: {'CASA Image' if os.path.isdir(imagename) else 'FITS File'}\nStokes: {stokes}"
214
+ )
215
+
216
+ # Update window title with filename
217
+ self.viewer.title = (
218
+ f"Solar Radio Image Viewer (Napari) - {os.path.basename(imagename)}"
219
+ )
220
+
221
+ except Exception as e:
222
+ print(f"Error loading data: {e}")
223
+ raise
224
+
225
+ def plot_image(self):
226
+ """Display the image in napari"""
227
+ if self.current_image_data is None:
228
+ return
229
+
230
+ data = self.current_image_data
231
+ cmap = "yellow"
232
+
233
+ # Remove existing layer if it exists
234
+ if self.image_layer is not None:
235
+ self.viewer.layers.remove(self.image_layer)
236
+
237
+ # Add the new image layer
238
+ self.image_layer = self.viewer.add_image(
239
+ data,
240
+ name="Image",
241
+ colormap=cmap,
242
+ )
243
+
244
+ # Update statistics
245
+ self.update_statistics()
246
+
247
+ # Reset view
248
+ self.viewer.reset_view()
249
+
250
+ def update_statistics(self):
251
+ """Update the statistics display"""
252
+ if self.current_image_data is None:
253
+ self.stats_label.setText("No image loaded")
254
+ return
255
+
256
+ data = self.current_image_data
257
+ min_val = np.nanmin(data)
258
+ max_val = np.nanmax(data)
259
+ mean_val = np.nanmean(data)
260
+ median_val = np.nanmedian(data)
261
+ std_val = np.nanstd(data[0:200, 0:200])
262
+ rms_val = np.sqrt(np.nanmean(data[0:200, 0:200] ** 2))
263
+ positive_DR = max_val / rms_val
264
+ negative_DR = min_val / rms_val
265
+
266
+ stats_text = (
267
+ f"Min: {min_val:.4g} Max: {max_val:.4g} Mean: {mean_val:.4g} Median: {median_val:.4g}\n"
268
+ f"Std Dev: {std_val:.4g} RMS: {rms_val:.4g}\n"
269
+ f"Positive DR: {positive_DR:.4g} Negative DR: {negative_DR:.4g}\n"
270
+ f"Shape: {data.shape}"
271
+ )
272
+
273
+ self.stats_label.setText(stats_text)
274
+
275
+ def on_stokes_changed(self, stokes):
276
+ """Handle changes to the Stokes parameter"""
277
+ if self.current_image_data is not None and hasattr(self, "imagename"):
278
+ try:
279
+ self.load_data(self.imagename)
280
+ self.plot_image()
281
+ except Exception as e:
282
+ print(f"Error updating Stokes parameter: {e}")
283
+ QMessageBox.critical(
284
+ self, "Error", f"Failed to update Stokes parameter: {str(e)}"
285
+ )
286
+
287
+ def on_threshold_changed(self, text):
288
+ """Handle changes to the threshold textbox"""
289
+ try:
290
+ if text == "":
291
+ threshold = 10.0
292
+ elif float(text) < 0:
293
+ print("Invalid threshold value")
294
+ QMessageBox.critical(
295
+ self,
296
+ "Error",
297
+ "Invalid threshold value. Please enter a valid number.",
298
+ )
299
+ return
300
+ else:
301
+ threshold = float(text)
302
+ self.load_data(self.imagename, threshold=threshold)
303
+ self.plot_image()
304
+ except ValueError:
305
+ print("Invalid threshold value")
306
+ QMessageBox.critical(
307
+ self, "Error", "Invalid threshold value. Please enter a valid number."
308
+ )
309
+
310
+ def show_help(self):
311
+ """Display help information about the Napari viewer"""
312
+ help_text = """
313
+ <h2>Napari Fast Viewer Help</h2>
314
+
315
+ <h3>Overview</h3>
316
+ <p>The Napari Fast Viewer is a lightweight tool for quickly visualizing solar radio images.</p>
317
+
318
+ <h3>Features</h3>
319
+ <ul>
320
+ <li><b>Fast Loading:</b> Quickly loads and displays FITS and CASA images</li>
321
+ <li><b>Stokes Parameters:</b> View different Stokes parameters (I, Q, U, V, etc.)</li>
322
+ <li><b>Threshold Control:</b> Adjust threshold for better visualization</li>
323
+ <li><b>Basic Statistics:</b> View basic image statistics</li>
324
+ </ul>
325
+
326
+ <h3>Usage</h3>
327
+ <ol>
328
+ <li>Select a file using the "Select File" button</li>
329
+ <li>Choose a Stokes parameter from the dropdown</li>
330
+ <li>Adjust the threshold value if needed</li>
331
+ </ol>
332
+
333
+ <h3>Command Line Usage</h3>
334
+ <p>You can launch this viewer directly from the command line:</p>
335
+ <pre>
336
+ solarviewer -f [image_file]
337
+ sv --fast [image_file]
338
+ </pre>
339
+ """
340
+ msg_box = QMessageBox(self)
341
+ msg_box.setWindowTitle("Napari Viewer Help")
342
+ msg_box.setTextFormat(Qt.RichText)
343
+ msg_box.setText(help_text)
344
+ msg_box.setStandardButtons(QMessageBox.Ok)
345
+ msg_box.exec_()
346
+
347
+
348
+ def main(imagename=None):
349
+ """Main function to run the application"""
350
+ # Check if QApplication already exists (when called from main app)
351
+ app = QApplication.instance()
352
+ if app is None:
353
+ app = QApplication(sys.argv)
354
+ standalone = True
355
+ else:
356
+ standalone = False
357
+
358
+ viewer = NapariViewer(imagename)
359
+
360
+ # Only exit if running standalone
361
+ if standalone:
362
+ sys.exit(app.exec_())
363
+ else:
364
+ app.exec_()
365
+
366
+
367
+ if __name__ == "__main__":
368
+ main()
@@ -0,0 +1,32 @@
1
+ """
2
+ NOAA Solar Events module - Fetch and display NOAA solar events.
3
+ """
4
+
5
+ from .noaa_events import (
6
+ SolarEvent,
7
+ EVENT_TYPES,
8
+ FLARE_CLASS_COLORS,
9
+ fetch_events_raw,
10
+ parse_events,
11
+ fetch_and_parse_events,
12
+ categorize_events,
13
+ get_event_statistics,
14
+ )
15
+
16
+ from .noaa_events_gui import (
17
+ NOAAEventsViewer,
18
+ show_noaa_events_viewer,
19
+ )
20
+
21
+ __all__ = [
22
+ "SolarEvent",
23
+ "EVENT_TYPES",
24
+ "FLARE_CLASS_COLORS",
25
+ "fetch_events_raw",
26
+ "parse_events",
27
+ "fetch_and_parse_events",
28
+ "categorize_events",
29
+ "get_event_statistics",
30
+ "NOAAEventsViewer",
31
+ "show_noaa_events_viewer",
32
+ ]