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,114 @@
1
+ from enum import StrEnum
2
+ from typing import Dict
3
+
4
+ from graxpert.astroimage import AstroImage
5
+ from graxpert.stretch import StretchParameters, calculate_mtf_stretch_parameters_for_image, stretch_all
6
+
7
+
8
+ class ImageTypes(StrEnum):
9
+ Original = "Original"
10
+ Gradient_Corrected = "Gradient-Corrected"
11
+ Background = "Background"
12
+ Deconvolved_Object_only = "Deconvolved Object-only"
13
+ Deconvolved_Stars_only = "Deconvolved Stars-only"
14
+ Denoised = "Denoised"
15
+
16
+
17
+ class AstroImageRepository:
18
+
19
+ images: Dict = {
20
+ ImageTypes.Original: None,
21
+ ImageTypes.Gradient_Corrected: None,
22
+ ImageTypes.Background: None,
23
+ ImageTypes.Deconvolved_Object_only: None,
24
+ ImageTypes.Deconvolved_Stars_only: None,
25
+ ImageTypes.Denoised: None,
26
+ }
27
+
28
+ def set(self, type: ImageTypes, image: AstroImage):
29
+ self.images[type] = image
30
+
31
+ def get(self, type: ImageTypes):
32
+ return self.images[type]
33
+
34
+ def stretch_all(self, stretch_params: StretchParameters, saturation: float):
35
+
36
+ if self.get(ImageTypes.Original) is None:
37
+ return
38
+
39
+ stretches = []
40
+
41
+ if not stretch_params.do_stretch:
42
+ for key, image in self.images.items():
43
+ if image is not None:
44
+ stretches.append(image.img_array)
45
+
46
+ else:
47
+
48
+ all_image_arrays = []
49
+ all_mtf_stretch_params = []
50
+
51
+ all_image_arrays.append(self.get(ImageTypes.Original).img_array)
52
+ all_mtf_stretch_params.append(calculate_mtf_stretch_parameters_for_image(stretch_params, self.get(ImageTypes.Original).img_array))
53
+
54
+ if self.get(ImageTypes.Gradient_Corrected) is not None and self.get(ImageTypes.Background) is not None:
55
+ all_image_arrays.append(self.get(ImageTypes.Gradient_Corrected).img_array)
56
+ all_mtf_stretch_params.append(calculate_mtf_stretch_parameters_for_image(stretch_params, self.get(ImageTypes.Gradient_Corrected).img_array))
57
+
58
+ all_image_arrays.append(self.get(ImageTypes.Background).img_array)
59
+ all_mtf_stretch_params.append(all_mtf_stretch_params[0])
60
+
61
+ if self.get(ImageTypes.Deconvolved_Object_only) is not None and self.get(ImageTypes.Gradient_Corrected) is None:
62
+ all_image_arrays.append(self.get(ImageTypes.Deconvolved_Object_only).img_array)
63
+ all_mtf_stretch_params.append(all_mtf_stretch_params[0])
64
+
65
+ elif self.get(ImageTypes.Deconvolved_Object_only) is not None and self.get(ImageTypes.Gradient_Corrected) is not None:
66
+ all_image_arrays.append(self.get(ImageTypes.Deconvolved_Object_only).img_array)
67
+ all_mtf_stretch_params.append(all_mtf_stretch_params[1])
68
+
69
+ if self.get(ImageTypes.Deconvolved_Stars_only) is not None and self.get(ImageTypes.Gradient_Corrected) is None:
70
+ all_image_arrays.append(self.get(ImageTypes.Deconvolved_Stars_only).img_array)
71
+ all_mtf_stretch_params.append(all_mtf_stretch_params[0])
72
+
73
+ elif self.get(ImageTypes.Deconvolved_Stars_only) is not None and self.get(ImageTypes.Gradient_Corrected) is not None:
74
+ all_image_arrays.append(self.get(ImageTypes.Deconvolved_Stars_only).img_array)
75
+ all_mtf_stretch_params.append(all_mtf_stretch_params[1])
76
+
77
+ if self.get(ImageTypes.Denoised) is not None and self.get(ImageTypes.Gradient_Corrected) is None:
78
+ all_image_arrays.append(self.get(ImageTypes.Denoised).img_array)
79
+ all_mtf_stretch_params.append(all_mtf_stretch_params[0])
80
+
81
+ elif self.get(ImageTypes.Denoised) is not None and self.get(ImageTypes.Gradient_Corrected) is not None:
82
+ all_image_arrays.append(self.get(ImageTypes.Denoised).img_array)
83
+ all_mtf_stretch_params.append(all_mtf_stretch_params[1])
84
+
85
+ stretches = stretch_all(all_image_arrays, all_mtf_stretch_params)
86
+
87
+ i = 0
88
+ for key, image in self.images.items():
89
+ if image is not None:
90
+ image.update_display_from_array(stretches[i], saturation)
91
+ i = i + 1
92
+
93
+ def crop_all(self, start_x: float, end_x: float, start_y: float, end_y: float):
94
+ for key, astroimg in self.images.items():
95
+ if astroimg is not None:
96
+ astroimg.crop(start_x, end_x, start_y, end_y)
97
+
98
+ def update_saturation(self, saturation):
99
+ for key, value in self.images.items():
100
+ if value is not None:
101
+ value.update_saturation(saturation)
102
+
103
+ def reset(self):
104
+ for key, value in self.images.items():
105
+ self.images[key] = None
106
+
107
+ def display_options(self):
108
+ display_options = []
109
+
110
+ for key, value in self.images.items():
111
+ if self.images[key] is not None:
112
+ display_options.append(key)
113
+
114
+ return display_options
graxpert/__init__.py ADDED
@@ -0,0 +1 @@
1
+
@@ -0,0 +1,215 @@
1
+ import logging
2
+ import os
3
+ import sys
4
+ import re
5
+ import shutil
6
+ import zipfile
7
+
8
+ from appdirs import user_data_dir
9
+ from minio import Minio
10
+ from packaging import version
11
+
12
+ try:
13
+ from graxpert.s3_secrets import endpoint, ro_access_key, ro_secret_key
14
+
15
+ client = Minio(endpoint, ro_access_key, ro_secret_key)
16
+ except Exception as e:
17
+ logging.exception(e)
18
+ client = None
19
+
20
+ from graxpert.ui.loadingframe import DynamicProgressThread
21
+
22
+ ai_models_dir = os.path.join(user_data_dir(appname="GraXpert"), "ai-models")
23
+ bge_ai_models_dir = os.path.join(user_data_dir(appname="GraXpert"), "bge-ai-models")
24
+
25
+ # old ai-models folder exists, rename to 'bge-ai-models'
26
+ if os.path.exists(ai_models_dir):
27
+ logging.warning(f"Older 'ai_models_dir' {ai_models_dir} exists. Renaming to {bge_ai_models_dir} due to introduction of new denoising models in GraXpert 3.")
28
+ try:
29
+ os.rename(ai_models_dir, bge_ai_models_dir)
30
+ except Exception as e:
31
+ logging.error(f"Renaming {ai_models_dir} to {bge_ai_models_dir} failed. {bge_ai_models_dir} will be newly created. Consider deleting obsolete {ai_models_dir} manually.")
32
+
33
+ os.makedirs(bge_ai_models_dir, exist_ok=True)
34
+
35
+ deconvolution_object_ai_models_dir = os.path.join(user_data_dir(appname="GraXpert"), "deconvolution-object-ai-models")
36
+ os.makedirs(deconvolution_object_ai_models_dir, exist_ok=True)
37
+ deconvolution_stars_ai_models_dir = os.path.join(user_data_dir(appname="GraXpert"), "deconvolution-stars-ai-models")
38
+ os.makedirs(deconvolution_stars_ai_models_dir, exist_ok=True)
39
+ denoise_ai_models_dir = os.path.join(user_data_dir(appname="GraXpert"), "denoise-ai-models")
40
+ os.makedirs(denoise_ai_models_dir, exist_ok=True)
41
+
42
+
43
+ # ui operations
44
+ def list_remote_versions(bucket_name):
45
+ if client is None:
46
+ return []
47
+ try:
48
+ objects = client.list_objects(bucket_name)
49
+ versions = []
50
+
51
+ for o in objects:
52
+ tags = client.get_object_tags(o.bucket_name, o.object_name)
53
+ if tags is not None and "ai-version" in tags:
54
+ versions.append(
55
+ {
56
+ "bucket": o.bucket_name,
57
+ "object": o.object_name,
58
+ "version": tags["ai-version"],
59
+ }
60
+ )
61
+ return versions
62
+
63
+ except Exception as e:
64
+ logging.exception(e)
65
+ finally:
66
+ return versions
67
+
68
+
69
+ def list_local_versions(ai_models_dir):
70
+ try:
71
+ model_dirs = [
72
+ {"path": os.path.join(ai_models_dir, f), "version": f}
73
+ for f in os.listdir(ai_models_dir)
74
+ if re.search(r"\d\.\d\.\d", f) and len(os.listdir(os.path.join(ai_models_dir, f))) > 0 # match semantic version
75
+ ]
76
+ return model_dirs
77
+ except Exception as e:
78
+ logging.exception(e)
79
+ return None
80
+
81
+
82
+ def latest_version(ai_models_dir, bucket_name):
83
+ try:
84
+ remote_versions = list_remote_versions(bucket_name)
85
+ except Exception as e:
86
+ remote_versions = []
87
+ logging.exception(e)
88
+ try:
89
+ local_versions = list_local_versions(ai_models_dir)
90
+ except Exception as e:
91
+ local_versions = []
92
+ logging.exception(e)
93
+ ai_options = set([])
94
+ ai_options.update([rv["version"] for rv in remote_versions])
95
+ ai_options.update(set([lv["version"] for lv in local_versions]))
96
+ ai_options = sorted(ai_options, key=lambda k: version.parse(k), reverse=True)
97
+ return ai_options[0]
98
+
99
+
100
+ def ai_model_path_from_version(ai_models_dir, local_version):
101
+ if local_version is None:
102
+ return None
103
+
104
+ return os.path.join(ai_models_dir, local_version, "model.onnx")
105
+
106
+
107
+ def compute_orphaned_local_versions(ai_models_dir):
108
+ remote_versions = list_remote_versions(ai_models_dir)
109
+
110
+ if remote_versions is None:
111
+ logging.warning("Could not fetch remote versions. Thus, aborting cleaning of local versions in {}. Consider manual cleaning".format(ai_models_dir))
112
+ return
113
+
114
+ local_versions = list_local_versions()
115
+
116
+ if local_versions is None:
117
+ logging.warning("Could not read local versions in {}. Thus, aborting cleaning. Consider manual cleaning".format(ai_models_dir))
118
+ return
119
+
120
+ orphaned_local_versions = [{"path": lv["path"], "version": lv["version"]} for lv in local_versions if lv["version"] not in [rv["version"] for rv in remote_versions]]
121
+
122
+ return orphaned_local_versions
123
+
124
+
125
+ def cleanup_orphaned_local_versions(orphaned_local_versions):
126
+ for olv in orphaned_local_versions:
127
+ try:
128
+ shutil.rmtree(olv["path"])
129
+ except Exception as e:
130
+ logging.exception(e)
131
+
132
+
133
+ def download_version(ai_models_dir, bucket_name, target_version, progress=None):
134
+ try:
135
+ remote_versions = list_remote_versions(bucket_name)
136
+ for r in remote_versions:
137
+ if target_version == r["version"]:
138
+ remote_version = r
139
+ break
140
+
141
+ ai_model_dir = os.path.join(ai_models_dir, "{}".format(remote_version["version"]))
142
+ os.makedirs(ai_model_dir, exist_ok=True)
143
+
144
+ ai_model_file = os.path.join(ai_model_dir, "model.onnx")
145
+ ai_model_zip = os.path.join(ai_model_dir, "model.zip")
146
+ client.fget_object(
147
+ remote_version["bucket"],
148
+ remote_version["object"],
149
+ ai_model_zip,
150
+ progress=DynamicProgressThread(callback=progress),
151
+ )
152
+
153
+ with zipfile.ZipFile(ai_model_zip, "r") as zip_ref:
154
+ zip_ref.extractall(ai_model_dir)
155
+
156
+ if not os.path.isfile(ai_model_file):
157
+ raise ValueError(f"Could not find ai 'model.onnx' file after extracting {ai_model_zip}")
158
+ os.remove(ai_model_zip)
159
+
160
+ except Exception as e:
161
+ # try to delete (rollback) ai_model_dir in case of errors
162
+ logging.exception(e)
163
+ try:
164
+ shutil.rmtree(ai_model_dir)
165
+ except Exception as e2:
166
+ logging.exception(e2)
167
+
168
+
169
+ def validate_local_version(ai_models_dir, local_version):
170
+ return os.path.isfile(os.path.join(ai_models_dir, local_version, "model.onnx"))
171
+
172
+
173
+ def get_execution_providers_ordered(gpu_acceleration=True):
174
+ if gpu_acceleration:
175
+ supported_providers = [
176
+ (
177
+ "OpenVINOExecutionProvider",
178
+ {
179
+ # per https://onnxruntime.ai/docs/execution-providers/OpenVINO-ExecutionProvider.html#summary-of-options
180
+ # "device_type": "HETERO:GPU,CPU,NPU AUTO:GPU,CPU,NPU MULTI:GPU,CPU,NPU", # Will prefer dGPU, fallback to iGPU, NPU or CPU with extra Intel specific optimizations
181
+ "device_type": "AUTO:GPU,CPU", # Will prefer dGPU, fallback to iGPU, NPU or CPU with extra Intel specific optimizations
182
+ }
183
+ ),
184
+ "ROCMExecutionProvider",
185
+ "DmlExecutionProvider",
186
+ (
187
+ "CoreMLExecutionProvider",
188
+ {
189
+ "flags": "COREML_FLAG_CREATE_MLPROGRAM",
190
+ },
191
+ ),
192
+ "CUDAExecutionProvider",
193
+ "CPUExecutionProvider",
194
+ ]
195
+ else:
196
+ supported_providers = ["CPUExecutionProvider"]
197
+
198
+ result = []
199
+ try:
200
+ import onnxruntime as ort
201
+ available = ort.get_available_providers()
202
+ for provider in supported_providers:
203
+ if isinstance(provider, tuple):
204
+ if provider[0] in available:
205
+ result.append(provider) # Append the entire tuple
206
+ else:
207
+ if provider in available:
208
+ result.append(provider)
209
+ return result
210
+ except Exception as e:
211
+ logging.error("Critical error! The required ONNX Runtime (AI library) package is misconfigured.\n" \
212
+ "Please read the README.md to confirm that you've selected the correct build for your hardware.\n" \
213
+ "If you are using one of our prebuilt executables, please file a bug with the following information:\n"
214
+ "{}".format(e))
215
+ sys.exit(1)
graxpert/app_state.py ADDED
@@ -0,0 +1,10 @@
1
+ from dataclasses import dataclass, field
2
+ from typing import List
3
+
4
+
5
+ @dataclass
6
+ class AppState:
7
+ background_points: List = field(default_factory=list)
8
+
9
+
10
+ INITIAL_STATE = AppState()
File without changes