graxpert 3.2.0a0.dev4__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 (58) hide show
  1. graxpert/AstroImageRepository.py +114 -0
  2. graxpert/__init__.py +1 -0
  3. graxpert/ai_model_handling.py +215 -0
  4. graxpert/app_state.py +10 -0
  5. graxpert/application/__init__.py +0 -0
  6. graxpert/application/app.py +741 -0
  7. graxpert/application/app_events.py +96 -0
  8. graxpert/application/eventbus.py +24 -0
  9. graxpert/astroimage.py +308 -0
  10. graxpert/background_extraction.py +290 -0
  11. graxpert/background_flood_selection.py +240 -0
  12. graxpert/background_grid_selection.py +64 -0
  13. graxpert/cmdline_tools.py +547 -0
  14. graxpert/commands.py +200 -0
  15. graxpert/deconvolution.py +176 -0
  16. graxpert/denoising.py +178 -0
  17. graxpert/grid_utils.py +38 -0
  18. graxpert/img/GraXpert_LOGO_Hauptvariante.png +0 -0
  19. graxpert/img/Icon.png +0 -0
  20. graxpert/img/LDN1235_original.jpg +0 -0
  21. graxpert/img/LDN1235_processed.jpg +0 -0
  22. graxpert/img/__init__.py +0 -0
  23. graxpert/img/gfx_numbers.png +0 -0
  24. graxpert/img/gfx_numbers.svg +737 -0
  25. graxpert/img/graXpert_Startbadge_Umbriel.png +0 -0
  26. graxpert/img/hourglass.png +0 -0
  27. graxpert/locales/__init__.py +0 -0
  28. graxpert/locales/de_DE/LC_MESSAGES/base.mo +0 -0
  29. graxpert/localization.py +39 -0
  30. graxpert/main.py +441 -0
  31. graxpert/mp_logging.py +97 -0
  32. graxpert/parallel_processing.py +3 -0
  33. graxpert/preferences.py +139 -0
  34. graxpert/radialbasisinterpolation.py +215 -0
  35. graxpert/resource_utils.py +35 -0
  36. graxpert/s3_secrets.py +16 -0
  37. graxpert/skyall.py +152 -0
  38. graxpert/stretch.py +151 -0
  39. graxpert/theme/__init__.py +0 -0
  40. graxpert/theme/graxpert-dark-blue.json +367 -0
  41. graxpert/ui/__init__.py +1 -0
  42. graxpert/ui/application_frame.py +115 -0
  43. graxpert/ui/canvas.py +558 -0
  44. graxpert/ui/left_menu.py +437 -0
  45. graxpert/ui/loadingframe.py +142 -0
  46. graxpert/ui/right_menu.py +315 -0
  47. graxpert/ui/statusbar.py +102 -0
  48. graxpert/ui/styling.py +23 -0
  49. graxpert/ui/tooltip.py +194 -0
  50. graxpert/ui/ui_events.py +19 -0
  51. graxpert/ui/widgets.py +297 -0
  52. graxpert/ui_scaling.py +13 -0
  53. graxpert/version.py +2 -0
  54. graxpert-3.2.0a0.dev4.dist-info/METADATA +219 -0
  55. graxpert-3.2.0a0.dev4.dist-info/RECORD +58 -0
  56. graxpert-3.2.0a0.dev4.dist-info/WHEEL +5 -0
  57. graxpert-3.2.0a0.dev4.dist-info/entry_points.txt +2 -0
  58. graxpert-3.2.0a0.dev4.dist-info/top_level.txt +1 -0
@@ -0,0 +1,96 @@
1
+ from enum import Enum, auto
2
+
3
+
4
+ class AppEvents(Enum):
5
+ # image loading
6
+ OPEN_FILE_DIALOG_REQUEST = auto()
7
+ LOAD_IMAGE_REQUEST = auto()
8
+ LOAD_IMAGE_BEGIN = auto()
9
+ LOAD_IMAGE_END = auto()
10
+ LOAD_IMAGE_ERROR = auto()
11
+ # image stretching
12
+ STRETCH_IMAGE_BEGIN = auto()
13
+ STRETCH_IMAGE_END = auto()
14
+ STRETCH_IMAGE_ERROR = auto()
15
+ # image saturation
16
+ CHANGE_SATURATION_REQUEST = auto()
17
+ CHANGE_SATURATION_BEGIN = auto()
18
+ CHANGE_SATURATION_END = auto()
19
+ # image display
20
+ UPDATE_DISPLAY_TYPE_REEQUEST = auto()
21
+ DISPLAY_TYPE_CHANGED = auto()
22
+ REDRAW_POINTS_REQUEST = auto()
23
+ # stretch options
24
+ STRETCH_OPTION_CHANGED = auto()
25
+ CHANNELS_LINKED_CHANGED = auto()
26
+ # sample selection
27
+ DISPLAY_PTS_CHANGED = auto()
28
+ BG_FLOOD_SELECTION_CHANGED = auto()
29
+ BG_PTS_CHANGED = auto()
30
+ BG_TOL_CHANGED = auto()
31
+ CREATE_GRID_REQUEST = auto()
32
+ CREATE_GRID_BEGIN = auto()
33
+ CREATE_GRID_END = auto()
34
+ RESET_POITS_REQUEST = auto()
35
+ RESET_POITS_BEGIN = auto()
36
+ RESET_POITS_END = auto()
37
+ # calculation
38
+ INTERPOL_TYPE_CHANGED = auto()
39
+ SMOTTHING_CHANGED = auto()
40
+ CALCULATE_REQUEST = auto()
41
+ CALCULATE_BEGIN = auto()
42
+ CALCULATE_PROGRESS = auto()
43
+ CALCULATE_END = auto()
44
+ CALCULATE_SUCCESS = auto()
45
+ CALCULATE_ERROR = auto()
46
+ # deconvolution
47
+ DECONVOLUTION_TYPE_CHANGED = auto()
48
+ DECONVOLUTION_STRENGTH_CHANGED = auto()
49
+ DECONVOLUTION_PSFSIZE_CHANGED = auto()
50
+ DECONVOLUTION_OPERATION_CHANGED = auto()
51
+ DECONVOLUTION_REQUEST = auto()
52
+ DECONVOLUTION_BEGIN = auto()
53
+ DECONVOLUTION_PROGRESS = auto()
54
+ DECONVOLUTION_END = auto()
55
+ DECONVOLUTION_SUCCESS = auto()
56
+ DECONVOLUTION_ERROR = auto()
57
+ # denoising
58
+ DENOISE_STRENGTH_CHANGED = auto()
59
+ DENOISE_THRESHOLD_CHANGED = auto()
60
+ DENOISE_REQUEST = auto()
61
+ DENOISE_BEGIN = auto()
62
+ DENOISE_PROGRESS = auto()
63
+ DENOISE_END = auto()
64
+ DENOISE_SUCCESS = auto()
65
+ DENOISE_ERROR = auto()
66
+ # saving
67
+ SAVE_AS_CHANGED = auto()
68
+ SAVE_STRETCHED_CHANGED = auto()
69
+ SAVE_REQUEST = auto()
70
+ SAVE_BEGIN = auto()
71
+ SAVE_END = auto()
72
+ SAVE_ERROR = auto()
73
+ # ai model handling
74
+ AI_DOWNLOAD_BEGIN = auto()
75
+ AI_DOWNLOAD_PROGRESS = auto()
76
+ AI_DOWNLOAD_END = auto()
77
+ AI_DOWNLOAD_ERROR = auto()
78
+ # bge ai model handling
79
+ BGE_AI_VERSION_CHANGED = auto()
80
+ # denoise ai model handling
81
+ DECONVOLUTION_OBJECT_AI_VERSION_CHANGED = auto()
82
+ DECONVOLUTION_STARS_AI_VERSION_CHANGED = auto()
83
+ # denoise ai model handling
84
+ DENOISE_AI_VERSION_CHANGED = auto()
85
+ # advanced settings
86
+ SAMPLE_SIZE_CHANGED = auto()
87
+ SAMPLE_COLOR_CHANGED = auto()
88
+ RBF_KERNEL_CHANGED = auto()
89
+ SPLINE_ORDER_CHANGED = auto()
90
+ CORRECTION_TYPE_CHANGED = auto()
91
+ LANGUAGE_CHANGED = auto()
92
+ SCALING_CHANGED = auto()
93
+ AI_BATCH_SIZE_CHANGED = auto()
94
+ AI_GPU_ACCELERATION_CHANGED = auto()
95
+ # process control
96
+ CANCEL_PROCESSING = auto()
@@ -0,0 +1,24 @@
1
+ class EventBus:
2
+ def __init__(self):
3
+ self.listeners = {}
4
+ # self.loop = asyncio.get_event_loop()
5
+
6
+ def add_listener(self, event_name, listener):
7
+ if not self.listeners.get(event_name, None):
8
+ self.listeners[event_name] = [listener]
9
+ else:
10
+ if not listener in self.listeners[event_name]:
11
+ self.listeners[event_name].insert(0, listener)
12
+
13
+ def remove_listener(self, event_name, listener):
14
+ self.listeners[event_name].remove(listener)
15
+ if len(self.listeners[event_name]) == 0:
16
+ del self.listeners[event_name]
17
+
18
+ def emit(self, event_name, event=None):
19
+ listeners = self.listeners.get(event_name, [])
20
+ for listener in listeners:
21
+ listener(event)
22
+
23
+
24
+ eventbus = EventBus()
graxpert/astroimage.py ADDED
@@ -0,0 +1,308 @@
1
+ import json
2
+ import logging
3
+ import os
4
+
5
+ import numpy as np
6
+ from astropy.io import fits
7
+ from PIL import Image, ImageEnhance
8
+ from skimage import exposure, img_as_float32, io
9
+ from skimage.util import img_as_uint
10
+ from xisf import XISF
11
+
12
+ from graxpert.app_state import AppState
13
+ from graxpert.preferences import Prefs, app_state_2_fitsheader
14
+ from graxpert.stretch import stretch, StretchParameters
15
+
16
+
17
+ class AstroImage:
18
+ def __init__(self, do_update_display=True):
19
+ self.img_array = None
20
+ self.img_display = None
21
+ self.img_display_saturated = None
22
+ self.img_format = None
23
+ self.fits_header = None
24
+ self.xisf_metadata = {}
25
+ self.image_metadata = {"FITSKeywords": {}}
26
+ self.do_update_display = do_update_display
27
+ self.width = 0
28
+ self.height = 0
29
+ self.roworder = "BOTTOM-UP"
30
+
31
+ def set_from_file(self, directory: str, stretch_params: StretchParameters, saturation: float):
32
+ self.img_format = os.path.splitext(directory)[1].lower()
33
+
34
+ img_array = None
35
+ if self.img_format == ".fits" or self.img_format == ".fit" or self.img_format == ".fts":
36
+ hdul = fits.open(directory)
37
+ img_array = np.copy(hdul[0].data)
38
+ self.fits_header = hdul[0].header
39
+ hdul.close()
40
+
41
+ if len(img_array.shape) == 3:
42
+ img_array = np.moveaxis(img_array, 0, -1)
43
+
44
+ if "ROWORDER" in self.fits_header:
45
+ self.roworder = self.fits_header["ROWORDER"]
46
+
47
+ elif self.img_format == ".xisf":
48
+ xisf = XISF(directory)
49
+ self.xisf_metadata = xisf.get_file_metadata()
50
+ self.image_metadata = xisf.get_images_metadata()[0]
51
+ self.fits_header = fits.Header()
52
+ self.xisf_imagedata_2_fitsheader()
53
+ img_array = np.copy(xisf.read_image(0))
54
+
55
+ entry = {"id": "GraXpert:ProcessingHistory", "type": "String", "value": "BackgroundExtraction"}
56
+ self.image_metadata["XISFProperties"]["GraXpert:ProcessingHistory"] = entry
57
+
58
+ else:
59
+ img_array = np.copy(io.imread(directory))
60
+ self.fits_header = fits.Header()
61
+
62
+ # Reshape greyscale picture to shape (y,x,1)
63
+ if len(img_array.shape) == 2:
64
+ img_array = np.array([img_array])
65
+ img_array = np.moveaxis(img_array, 0, -1)
66
+
67
+ # Use 32 bit float with range (0,1) for internal calculations
68
+ img_array = img_as_float32(img_array)
69
+
70
+ if np.min(img_array) < 0 or np.max(img_array > 1):
71
+ img_array = exposure.rescale_intensity(img_array, out_range=(0, 1))
72
+
73
+ self.img_array = img_array
74
+ self.width = self.img_array.shape[1]
75
+ self.height = self.img_array.shape[0]
76
+
77
+ if self.do_update_display:
78
+ self.update_display(stretch_params, saturation)
79
+
80
+ return
81
+
82
+ def set_from_array(self, array):
83
+ self.img_array = array
84
+ self.width = self.img_array.shape[1]
85
+ self.height = self.img_array.shape[0]
86
+ return
87
+
88
+ def update_display(self, stretch_params: StretchParameters, saturation: float):
89
+ img_display = self.stretch(stretch_params)
90
+ img_display = img_display * 255
91
+
92
+ # if self.roworder == "TOP-DOWN":
93
+ # img_display = np.flip(img_display, axis=0)
94
+
95
+ if img_display.shape[2] == 1:
96
+ self.img_display = Image.fromarray(img_display[:, :, 0].astype(np.uint8))
97
+ else:
98
+ self.img_display = Image.fromarray(img_display.astype(np.uint8))
99
+
100
+ self.update_saturation(saturation)
101
+
102
+ return
103
+
104
+ def update_display_from_array(self, img_display, saturation):
105
+ img_display = img_display * 255
106
+
107
+ # if self.roworder == "TOP-DOWN":
108
+ # img_display = np.flip(img_display, axis=0)
109
+
110
+ if img_display.shape[2] == 1:
111
+ self.img_display = Image.fromarray(img_display[:, :, 0].astype(np.uint8))
112
+ else:
113
+ self.img_display = Image.fromarray(img_display.astype(np.uint8))
114
+
115
+ self.update_saturation(saturation)
116
+
117
+ return
118
+
119
+ def stretch(self, stretch_params: StretchParameters):
120
+ if stretch_params.do_stretch:
121
+ return np.clip(stretch(self.img_array, stretch_params), 0.0, 1.0)
122
+ else:
123
+ return np.clip(self.img_array, 0.0, 1.0)
124
+
125
+ def crop(self, startx, endx, starty, endy):
126
+ self.img_array = self.img_array[starty:endy, startx:endx, :]
127
+ self.img_display = self.img_display.crop((startx, starty, endx, endy))
128
+ self.img_display_saturated = self.img_display_saturated.crop((startx, starty, endx, endy))
129
+ self.width = self.img_array.shape[1]
130
+ self.height = self.img_array.shape[0]
131
+ return
132
+
133
+ def update_fits_header(self, original_header, background_mean, prefs: Prefs, app_state: AppState):
134
+ if original_header is None:
135
+ self.fits_header = fits.Header()
136
+ else:
137
+ self.fits_header = original_header
138
+
139
+ self.fits_header["BG-EXTR"] = "GraXpert"
140
+ self.fits_header["CBG-1"] = background_mean
141
+ self.fits_header["CBG-2"] = background_mean
142
+ self.fits_header["CBG-3"] = background_mean
143
+ self.fits_header = app_state_2_fitsheader(prefs, app_state, self.fits_header)
144
+
145
+ if "ROWORDER" in self.fits_header:
146
+ self.roworder = self.fits_header["ROWORDER"]
147
+
148
+ def save(self, dir, saveas_type):
149
+ if self.img_array is None:
150
+ return
151
+
152
+ if saveas_type == "16 bit Tiff" or saveas_type == "16 bit Fits" or saveas_type == "16 bit XISF":
153
+ image_converted = img_as_uint(self.img_array)
154
+ else:
155
+ image_converted = self.img_array.astype(np.float32)
156
+
157
+ if saveas_type != "16 bit XISF" and saveas_type != "32 bit XISF":
158
+ if image_converted.shape[-1] == 3:
159
+ if saveas_type == "16 bit Fits" or saveas_type == "32 bit Fits":
160
+ image_converted = np.moveaxis(image_converted, -1, 0)
161
+ else:
162
+ image_converted = image_converted[:, :, 0]
163
+
164
+ if saveas_type == "16 bit Tiff" or saveas_type == "32 bit Tiff":
165
+ io.imsave(dir, image_converted)
166
+
167
+ elif saveas_type == "16 bit XISF" or saveas_type == "32 bit XISF":
168
+ self.update_xisf_imagedata()
169
+ XISF.write(dir, image_converted, creator_app="GraXpert", image_metadata=self.image_metadata, xisf_metadata=self.xisf_metadata)
170
+ else:
171
+ hdu = fits.PrimaryHDU(data=image_converted, header=self.fits_header)
172
+ hdul = fits.HDUList([hdu])
173
+ hdul.writeto(dir, output_verify="warn", overwrite=True)
174
+ hdul.close()
175
+
176
+ return
177
+
178
+ def save_stretched(self, dir, saveas_type, stretch_params):
179
+ if self.img_array is None:
180
+ return
181
+
182
+ if self.fits_header is not None:
183
+ self.fits_header["STRETCH"] = stretch_params.stretch_option
184
+
185
+ stretched_img = self.stretch(stretch_params)
186
+
187
+ if saveas_type == "16 bit Tiff" or saveas_type == "16 bit Fits" or saveas_type == "16 bit XISF":
188
+ image_converted = img_as_uint(stretched_img)
189
+ else:
190
+ image_converted = stretched_img.astype(np.float32)
191
+
192
+ if saveas_type != "16 bit XISF" and saveas_type != "32 bit XISF":
193
+ if image_converted.shape[-1] == 3:
194
+ if saveas_type == "16 bit Fits" or saveas_type == "32 bit Fits":
195
+ image_converted = np.moveaxis(image_converted, -1, 0)
196
+ else:
197
+ image_converted = image_converted[:, :, 0]
198
+
199
+ if saveas_type == "16 bit Tiff" or saveas_type == "32 bit Tiff":
200
+ io.imsave(dir, image_converted)
201
+
202
+ elif saveas_type == "16 bit XISF" or saveas_type == "32 bit XISF":
203
+ self.update_xisf_imagedata()
204
+ XISF.write(dir, image_converted, creator_app="GraXpert", image_metadata=self.image_metadata, xisf_metadata=self.xisf_metadata)
205
+ else:
206
+ hdu = fits.PrimaryHDU(data=image_converted, header=self.fits_header)
207
+ hdul = fits.HDUList([hdu])
208
+ hdul.writeto(dir, output_verify="warn", overwrite=True)
209
+ hdul.close()
210
+
211
+ return
212
+
213
+ def get_local_median(self, img_point):
214
+ sample_radius = 2
215
+ y1 = int(np.amax([img_point[1] - sample_radius, 0]))
216
+ y2 = int(np.amin([img_point[1] + sample_radius, self.height]))
217
+ x1 = int(np.amax([img_point[0] - sample_radius, 0]))
218
+ x2 = int(np.amin([img_point[0] + sample_radius, self.width]))
219
+
220
+ if self.img_array.shape[-1] == 3:
221
+ R = np.median(self.img_array[y1:y2, x1:x2, 0])
222
+ G = np.median(self.img_array[y1:y2, x1:x2, 1])
223
+ B = np.median(self.img_array[y1:y2, x1:x2, 2])
224
+
225
+ return [R, G, B]
226
+
227
+ if self.img_array.shape[-1] == 1:
228
+ L = np.median(self.img_array[x1:x2, y1:y2, 0])
229
+
230
+ return L
231
+
232
+ def copy_metadata(self, source_img):
233
+ self.xisf_metadata = source_img.xisf_metadata
234
+ self.image_metadata = source_img.image_metadata
235
+
236
+ def update_saturation(self, saturation):
237
+ self.img_display_saturated = self.img_display
238
+
239
+ if self.img_array.shape[-1] == 3:
240
+ self.img_display_saturated = ImageEnhance.Color(self.img_display)
241
+ self.img_display_saturated = self.img_display_saturated.enhance(saturation)
242
+
243
+ return
244
+
245
+ def update_xisf_imagedata(self):
246
+ unique_keys = list(dict.fromkeys(self.fits_header.keys()))
247
+
248
+ for key in unique_keys:
249
+ if key == "BG-PTS":
250
+ try:
251
+ bg_pts = json.loads(self.fits_header["BG-PTS"])
252
+
253
+ for i in range(len(bg_pts)):
254
+ self.image_metadata["FITSKeywords"]["BG-PTS" + str(i)] = [{"value": bg_pts[i], "comment": ""}]
255
+ except:
256
+ logging.warning("Could not transfer background points from fits header to xisf image metadata", stack_info=True)
257
+ else:
258
+ value = str(self.fits_header[key]).splitlines()
259
+ comment = str(self.fits_header.comments[key]).splitlines()
260
+
261
+ entry = []
262
+
263
+ for i in range(max(len(comment), len(value))):
264
+ value_i = ""
265
+ comment_i = ""
266
+
267
+ if i < len(comment):
268
+ comment_i = comment[i]
269
+ if i < len(value):
270
+ value_i = value[i]
271
+
272
+ entry.append({"value": value_i, "comment": comment_i})
273
+
274
+ if len(entry) == 0:
275
+ entry = [{"value": "", "comment": ""}]
276
+
277
+ self.image_metadata["FITSKeywords"][key] = entry
278
+
279
+ def xisf_imagedata_2_fitsheader(self):
280
+ commentary_keys = ["HISTORY", "COMMENT", ""]
281
+
282
+ bg_pts = []
283
+ for key in self.image_metadata["FITSKeywords"].keys():
284
+ if key.startswith("BG-PTS"):
285
+ try:
286
+ bg_pts.append(json.loads(self.image_metadata["FITSKeywords"][key][0]["value"]))
287
+ except:
288
+ logging.warning(f"Could not load background points from xisf image metadata. Affected entry: {self.image_metadata['FITSKeywords'][key]}", stack_info=True)
289
+
290
+ else:
291
+ for i in range(len(self.image_metadata["FITSKeywords"][key])):
292
+ value = self.image_metadata["FITSKeywords"][key][i]["value"]
293
+ comment = self.image_metadata["FITSKeywords"][key][i]["comment"]
294
+
295
+ # Commentary cards have to comments in Fits standard
296
+ if key in commentary_keys:
297
+ if value == "":
298
+ value = comment
299
+
300
+ if value.isdigit():
301
+ value = int(value)
302
+ elif value.isdecimal():
303
+ value = float(value)
304
+
305
+ self.fits_header[key] = (value, comment)
306
+
307
+ if len(bg_pts) > 0:
308
+ self.fits_header["BG-PTS"] = str(bg_pts)