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,315 @@
1
+ import tkinter as tk
2
+ import webbrowser
3
+ from tkinter import messagebox
4
+
5
+ import customtkinter as ctk
6
+ from customtkinter import CTkFont, CTkImage, CTkLabel, CTkSwitch, CTkTextbox
7
+ from packaging import version
8
+
9
+ from graxpert.ai_model_handling import bge_ai_models_dir, deconvolution_object_ai_models_dir, deconvolution_stars_ai_models_dir, denoise_ai_models_dir, list_local_versions, list_remote_versions
10
+ from graxpert.application.app import graxpert
11
+ from graxpert.application.app_events import AppEvents
12
+ from graxpert.application.eventbus import eventbus
13
+ from graxpert.localization import _, lang
14
+ from graxpert.resource_utils import resource_image
15
+ from graxpert.s3_secrets import bge_bucket_name, deconvolution_object_bucket_name, deconvolution_stars_bucket_name, denoise_bucket_name
16
+ from graxpert.ui.widgets import GraXpertOptionMenu, GraXpertScrollableFrame, ProcessingStep, ValueSlider, padx, pady
17
+
18
+
19
+ class HelpText(CTkTextbox):
20
+ def __init__(self, master, text="", rows=1, font=None, **kwargs):
21
+ super().__init__(master, width=250, fg_color="transparent", wrap="word", activate_scrollbars=False, **kwargs)
22
+ self.configure(height=self._font.metrics("linespace") * rows + 4 * pady)
23
+ self.insert("0.0", text)
24
+
25
+
26
+ class RightFrameBase(GraXpertScrollableFrame):
27
+ def __init__(self, master, **kwargs):
28
+ super().__init__(master, **kwargs)
29
+ self.row = 0
30
+ self.heading_font = CTkFont(size=15, weight="bold")
31
+ self.heading_font2 = CTkFont(size=13, weight="bold")
32
+
33
+ def nrow(self):
34
+ self.row += 1
35
+ return self.row
36
+
37
+
38
+ class HelpFrame(RightFrameBase):
39
+ def __init__(self, master, **kwargs):
40
+ super().__init__(master, **kwargs)
41
+
42
+ self.img = resource_image("GraXpert_LOGO_Hauptvariante.png")
43
+ self.create_and_place_children()
44
+ self.setup_layout()
45
+
46
+ def _on_destroy(self, event):
47
+ """This method is called when the widget is destroyed."""
48
+ if self.img:
49
+ self.img.close()
50
+
51
+ def default_grid(self):
52
+ return {"column": 0, "row": self.nrow(), "padx": padx, "pady": pady, "sticky": tk.EW}
53
+
54
+ def create_and_place_children(self):
55
+
56
+ logo = CTkImage(
57
+ light_image=self.img,
58
+ dark_image=self.img,
59
+ size=(225, 111),
60
+ )
61
+
62
+ CTkLabel(self, image=logo, text="").grid(column=0, row=self.nrow(), padx=padx, pady=pady, sticky=tk.NSEW)
63
+ CTkLabel(self, text=_("Instructions"), font=self.heading_font).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
64
+
65
+ ProcessingStep(self, number=1, indent=0, title=_(" Loading")).grid(**self.default_grid())
66
+ HelpText(self, text=_("Load your image.")).grid(**self.default_grid())
67
+
68
+ ProcessingStep(self, number=2, indent=0, title=_(" Stretch Options")).grid(**self.default_grid())
69
+ HelpText(self, rows=2, text=_("Stretch your image if necessary to reveal gradients.")).grid(**self.default_grid())
70
+
71
+ ProcessingStep(self, number=3, indent=0, title=_(" Sample Selection")).grid(**self.default_grid())
72
+ HelpText(
73
+ self,
74
+ rows=5,
75
+ text=_("Select background points\n a) manually with left click\n b) automatically via grid (grid selection)" "\nYou can remove already set points by right clicking on them."),
76
+ ).grid(**self.default_grid())
77
+
78
+ ProcessingStep(self, number=4, indent=0, title=_(" Calculation")).grid(**self.default_grid())
79
+ HelpText(self, rows=2, text=_("Click on Calculate Background to get the processed image.")).grid(**self.default_grid())
80
+
81
+ ProcessingStep(self, number=5, indent=0, title=_(" Saving")).grid(**self.default_grid())
82
+ HelpText(self, text=_("Save the processed image.")).grid(**self.default_grid())
83
+
84
+ CTkLabel(self, text=_("Keybindings"), font=self.heading_font).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
85
+
86
+ HelpText(self, text=_("Left click on picture: Set sample point")).grid(**self.default_grid())
87
+ HelpText(self, rows=2, text=_("Left click on picture + drag: Move picture")).grid(**self.default_grid())
88
+ HelpText(self, rows=2, text=_("Left click on sample point + drag: Move sample point")).grid(**self.default_grid())
89
+ HelpText(self, rows=2, text=_("Right click on sample point: Delete sample point")).grid(**self.default_grid())
90
+ HelpText(self, text=_("Mouse wheel: Zoom")).grid(**self.default_grid())
91
+ HelpText(self, rows=3, text=_("Ctrl+Z/Y: Undo/Redo sample point")).grid(**self.default_grid())
92
+
93
+ CTkLabel(self, text=_("Licenses"), font=self.heading_font).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
94
+
95
+ def callback(url):
96
+ webbrowser.open_new(url)
97
+
98
+ row = self.nrow()
99
+ HelpText(self, text=_("GraXpert is licensed under GPL-3:")).grid(column=0, row=row, padx=padx, pady=pady, sticky=tk.W)
100
+ url_link_1 = "https://raw.githubusercontent.com/Steffenhir/GraXpert/main/License.md"
101
+ url_label_1 = CTkLabel(self, text="<Link>", text_color="dodger blue")
102
+ url_label_1.grid(column=0, row=row, padx=padx, sticky=tk.E)
103
+ url_label_1.bind("<Button-1>", lambda e: callback(url_link_1))
104
+
105
+ row = self.nrow()
106
+ HelpText(self, rows=2, text=_("Background Extraction AI models are licensed under CC BY-NC-SA:")).grid(column=0, row=row, padx=padx, pady=pady, sticky=tk.W)
107
+ url_link_2 = "https://raw.githubusercontent.com/Steffenhir/GraXpert/main/licenses/BGE-Model-LICENSE.html"
108
+ url_label_2 = CTkLabel(self, text="<Link>", text_color="dodger blue")
109
+ url_label_2.grid(column=0, row=row, padx=padx, sticky=tk.E)
110
+ url_label_2.bind("<Button-1>", lambda e: callback(url_link_2))
111
+
112
+ row = self.nrow()
113
+ HelpText(self, rows=2, text=_("Deconvolution AI models are licensed under CC BY-NC-SA:")).grid(column=0, row=row, padx=padx, pady=pady, sticky=tk.W)
114
+ url_link_3 = "https://raw.githubusercontent.com/Steffenhir/GraXpert/main/licenses/Deconvolution-Model-LICENSE.html"
115
+ url_label_3 = CTkLabel(self, text="<Link>", text_color="dodger blue")
116
+ url_label_3.grid(column=0, row=row, padx=padx, sticky=tk.E)
117
+ url_label_3.bind("<Button-1>", lambda e: callback(url_link_3))
118
+
119
+ row = self.nrow()
120
+ HelpText(self, rows=2, text=_("Denoising AI models are licensed under CC BY-NC-SA:")).grid(column=0, row=row, padx=padx, pady=pady, sticky=tk.W)
121
+ url_link_3 = "https://raw.githubusercontent.com/Steffenhir/GraXpert/main/licenses/Denoise-Model-LICENSE.html"
122
+ url_label_3 = CTkLabel(self, text="<Link>", text_color="dodger blue")
123
+ url_label_3.grid(column=0, row=row, padx=padx, sticky=tk.E)
124
+ url_label_3.bind("<Button-1>", lambda e: callback(url_link_3))
125
+
126
+ def setup_layout(self):
127
+ self.columnconfigure(0, weight=1)
128
+ self.rowconfigure(0, weight=1)
129
+
130
+
131
+ class AdvancedFrame(RightFrameBase):
132
+ def __init__(self, master, **kwargs):
133
+ super().__init__(master, **kwargs)
134
+
135
+ # sample points
136
+ self.sample_size = tk.IntVar()
137
+ self.sample_size.set(graxpert.prefs.sample_size)
138
+ self.sample_size.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.SAMPLE_SIZE_CHANGED, {"sample_size": self.sample_size.get()}))
139
+
140
+ self.sample_color = tk.IntVar()
141
+ self.sample_color.set(graxpert.prefs.sample_color)
142
+ self.sample_color.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.SAMPLE_COLOR_CHANGED, {"sample_color": self.sample_color.get()}))
143
+
144
+ # interpolation
145
+ self.rbf_kernels = ["thin_plate", "quintic", "cubic", "linear"]
146
+ self.rbf_kernel = tk.StringVar()
147
+ self.rbf_kernel.set(graxpert.prefs.RBF_kernel)
148
+ self.rbf_kernel.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.RBF_KERNEL_CHANGED, {"RBF_kernel": self.rbf_kernel.get()}))
149
+
150
+ self.spline_orders = ["1", "2", "3", "4", "5"]
151
+ self.spline_order = tk.StringVar()
152
+ self.spline_order.set(str(graxpert.prefs.spline_order))
153
+ self.spline_order.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.SPLINE_ORDER_CHANGED, {"spline_order": int(self.spline_order.get())}))
154
+
155
+ self.corr_types = ["Subtraction", "Division"]
156
+ self.corr_type = tk.StringVar()
157
+ self.corr_type.set(graxpert.prefs.corr_type)
158
+ self.corr_type.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.CORRECTION_TYPE_CHANGED, {"corr_type": self.corr_type.get()}))
159
+
160
+ # interface
161
+ self.langs = ["English", "Deutsch"]
162
+ self.lang = tk.StringVar()
163
+ self.lang.set(graxpert.prefs.lang)
164
+ self.lang.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.LANGUAGE_CHANGED, {"lang": self.lang.get()}))
165
+
166
+ self.scaling = tk.DoubleVar()
167
+ self.scaling.set(graxpert.prefs.scaling)
168
+ self.scaling.trace_add("write", self.on_scaling_change)
169
+
170
+ # bge ai model
171
+ bge_remote_versions = list_remote_versions(bge_bucket_name)
172
+ bge_local_versions = list_local_versions(bge_ai_models_dir)
173
+ self.bge_ai_options = set([])
174
+ self.bge_ai_options.update([rv["version"] for rv in bge_remote_versions])
175
+ self.bge_ai_options.update([lv["version"] for lv in bge_local_versions])
176
+ self.bge_ai_options = sorted(self.bge_ai_options, key=lambda k: version.parse(k), reverse=True)
177
+
178
+ self.bge_ai_version = tk.StringVar(master)
179
+ self.bge_ai_version.set("None") # default value
180
+ if graxpert.prefs.bge_ai_version is not None:
181
+ self.bge_ai_version.set(graxpert.prefs.bge_ai_version)
182
+ else:
183
+ self.bge_ai_options.insert(0, "None")
184
+ self.bge_ai_version.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.BGE_AI_VERSION_CHANGED, {"bge_ai_version": self.bge_ai_version.get()}))
185
+
186
+ # object deconvolution ai models
187
+ deconvolution_object_ai_remote_versions = list_remote_versions(deconvolution_object_bucket_name)
188
+ deconvolution_object_ai_local_versions = list_local_versions(deconvolution_object_ai_models_dir)
189
+ self.deconvolution_object_ai_options = set([])
190
+ self.deconvolution_object_ai_options.update([rv["version"] for rv in deconvolution_object_ai_remote_versions])
191
+ self.deconvolution_object_ai_options.update([lv["version"] for lv in deconvolution_object_ai_local_versions])
192
+ self.deconvolution_object_ai_options = sorted(self.deconvolution_object_ai_options, key=lambda k: version.parse(k), reverse=True)
193
+
194
+ self.deconvolution_object_ai_version = tk.StringVar(master)
195
+ self.deconvolution_object_ai_version.set("None") # default value
196
+ if graxpert.prefs.deconvolution_object_ai_version is not None:
197
+ self.deconvolution_object_ai_version.set(graxpert.prefs.deconvolution_object_ai_version)
198
+ else:
199
+ self.deconvolution_object_ai_options.insert(0, "None")
200
+ self.deconvolution_object_ai_version.trace_add(
201
+ "write", lambda a, b, c: eventbus.emit(AppEvents.DECONVOLUTION_OBJECT_AI_VERSION_CHANGED, {"deconvolution_object_ai_version": self.deconvolution_object_ai_version.get()})
202
+ )
203
+
204
+ # stars deconvolution ai models
205
+ deconvolution_stars_ai_remote_versions = list_remote_versions(deconvolution_stars_bucket_name)
206
+ deconvolution_stars_ai_local_versions = list_local_versions(deconvolution_stars_ai_models_dir)
207
+ self.deconvolution_stars_ai_options = set([])
208
+ self.deconvolution_stars_ai_options.update([rv["version"] for rv in deconvolution_stars_ai_remote_versions])
209
+ self.deconvolution_stars_ai_options.update([lv["version"] for lv in deconvolution_stars_ai_local_versions])
210
+ self.deconvolution_stars_ai_options = sorted(self.deconvolution_stars_ai_options, key=lambda k: version.parse(k), reverse=True)
211
+
212
+ self.deconvolution_stars_ai_version = tk.StringVar(master)
213
+ self.deconvolution_stars_ai_version.set("None") # default value
214
+ if graxpert.prefs.deconvolution_stars_ai_version is not None:
215
+ self.deconvolution_stars_ai_version.set(graxpert.prefs.deconvolution_stars_ai_version)
216
+ else:
217
+ self.deconvolution_stars_ai_options.insert(0, "None")
218
+ self.deconvolution_stars_ai_version.trace_add(
219
+ "write", lambda a, b, c: eventbus.emit(AppEvents.DECONVOLUTION_STARS_AI_VERSION_CHANGED, {"deconvolution_stars_ai_version": self.deconvolution_stars_ai_version.get()})
220
+ )
221
+
222
+ # denoise ai model
223
+ denoise_remote_versions = list_remote_versions(denoise_bucket_name)
224
+ denoise_local_versions = list_local_versions(denoise_ai_models_dir)
225
+ self.denoise_ai_options = set([])
226
+ self.denoise_ai_options.update([rv["version"] for rv in denoise_remote_versions])
227
+ self.denoise_ai_options.update([lv["version"] for lv in denoise_local_versions])
228
+ self.denoise_ai_options = sorted(self.denoise_ai_options, key=lambda k: version.parse(k), reverse=True)
229
+
230
+ self.denoise_ai_version = tk.StringVar(master)
231
+ self.denoise_ai_version.set("None") # default value
232
+ if graxpert.prefs.denoise_ai_version is not None:
233
+ self.denoise_ai_version.set(graxpert.prefs.denoise_ai_version)
234
+ else:
235
+ self.denoise_ai_options.insert(0, "None")
236
+ self.denoise_ai_version.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.DENOISE_AI_VERSION_CHANGED, {"denoise_ai_version": self.denoise_ai_version.get()}))
237
+
238
+ # ai settings
239
+ self.ai_batch_size_options = ["1", "2", "4", "8", "16", "32"]
240
+ self.ai_batch_size = tk.IntVar()
241
+ self.ai_batch_size.set(graxpert.prefs.ai_batch_size)
242
+ self.ai_batch_size.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.AI_BATCH_SIZE_CHANGED, {"ai_batch_size": self.ai_batch_size.get()}))
243
+
244
+ self.ai_gpu_acceleration = tk.BooleanVar()
245
+ self.ai_gpu_acceleration.set(graxpert.prefs.ai_gpu_acceleration)
246
+ self.ai_gpu_acceleration.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.AI_GPU_ACCELERATION_CHANGED, {"ai_gpu_acceleration": self.ai_gpu_acceleration.get()}))
247
+
248
+ self.create_and_place_children()
249
+ self.setup_layout()
250
+
251
+ def on_scaling_change(self, a, b, c):
252
+ eventbus.emit(AppEvents.SCALING_CHANGED, {"scaling": self.scaling.get()})
253
+ ctk.set_widget_scaling(self.scaling.get())
254
+
255
+ def create_and_place_children(self):
256
+ CTkLabel(self, text=_("Advanced Settings"), font=self.heading_font).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
257
+
258
+ # sample points
259
+ CTkLabel(self, text=_("Sample Points"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
260
+
261
+ ValueSlider(self, variable=self.sample_size, variable_name=_("Sample size"), min_value=5, max_value=50, precision=0).grid(**self.default_grid())
262
+ ValueSlider(self, variable=self.sample_color, variable_name=_("Sample color"), min_value=0, max_value=360, precision=0).grid(**self.default_grid())
263
+
264
+ # interpolation
265
+ CTkLabel(self, text=_("Interpolation"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
266
+
267
+ CTkLabel(self, text=_("RBF Kernel")).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
268
+ GraXpertOptionMenu(self, variable=self.rbf_kernel, values=self.rbf_kernels).grid(**self.default_grid())
269
+
270
+ CTkLabel(self, text=_("Spline order")).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
271
+ GraXpertOptionMenu(self, variable=self.spline_order, values=self.spline_orders).grid(**self.default_grid())
272
+
273
+ CTkLabel(self, text=_("Correction")).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
274
+ GraXpertOptionMenu(self, variable=self.corr_type, values=self.corr_types).grid(**self.default_grid())
275
+
276
+ # interface
277
+ CTkLabel(self, text=_("Interface"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
278
+
279
+ def lang_change(lang):
280
+ messagebox.showerror("", _("Please restart the program to change the language."))
281
+
282
+ CTkLabel(self, text=_("Language")).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
283
+ GraXpertOptionMenu(self, variable=self.lang, values=self.langs).grid(**self.default_grid())
284
+
285
+ ValueSlider(self, variable=self.scaling, variable_name=_("Scaling"), min_value=1, max_value=2, precision=1).grid(**self.default_grid())
286
+
287
+ # bge ai model
288
+ CTkLabel(self, text=_("Background Extraction AI-Model"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
289
+ GraXpertOptionMenu(self, variable=self.bge_ai_version, values=self.bge_ai_options).grid(**self.default_grid())
290
+
291
+ # object-deconvolution ai model
292
+ CTkLabel(self, text=_("Object Deconvolution AI-Model"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
293
+ GraXpertOptionMenu(self, variable=self.deconvolution_object_ai_version, values=self.deconvolution_object_ai_options).grid(**self.default_grid())
294
+
295
+ # stars-deconvolution ai model
296
+ CTkLabel(self, text=_("Stars Deconvolution AI-Model"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
297
+ GraXpertOptionMenu(self, variable=self.deconvolution_stars_ai_version, values=self.deconvolution_stars_ai_options).grid(**self.default_grid())
298
+
299
+ # denoise ai model
300
+ CTkLabel(self, text=_("Denoising AI-Model"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
301
+ GraXpertOptionMenu(self, variable=self.denoise_ai_version, values=self.denoise_ai_options).grid(**self.default_grid())
302
+
303
+ # ai settings
304
+ CTkLabel(self, text=_("AI inference batch size"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
305
+ GraXpertOptionMenu(self, variable=self.ai_batch_size, values=self.ai_batch_size_options).grid(**self.default_grid())
306
+
307
+ CTkLabel(self, text=_("AI Hardware Acceleration"), font=self.heading_font2).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
308
+ CTkSwitch(self, text=_("Enable Acceleration"), variable=self.ai_gpu_acceleration).grid(column=0, row=self.nrow(), pady=pady, sticky=tk.N)
309
+
310
+ def setup_layout(self):
311
+ self.columnconfigure(0, weight=1)
312
+ self.rowconfigure(0, weight=1)
313
+
314
+ def default_grid(self):
315
+ return {"column": 0, "row": self.nrow(), "padx": padx, "pady": pady}
@@ -0,0 +1,102 @@
1
+ import tkinter as tk
2
+
3
+ from customtkinter import CTkFrame, CTkLabel, StringVar
4
+
5
+ import graxpert.ui.tooltip as tooltip
6
+ from graxpert.application.app import graxpert
7
+ from graxpert.application.app_events import AppEvents
8
+ from graxpert.application.eventbus import eventbus
9
+ from graxpert.AstroImageRepository import ImageTypes
10
+ from graxpert.localization import _
11
+ from graxpert.ui.ui_events import UiEvents
12
+ from graxpert.ui.widgets import GraXpertCheckbox, GraXpertOptionMenu, ProcessingStep, ValueSlider, default_label_width, padx, pady
13
+
14
+
15
+ class StatusBar(CTkFrame):
16
+ def __init__(self, master, **kwargs):
17
+ super().__init__(master, **kwargs)
18
+
19
+ self.stretch_options = ["No Stretch", "10% Bg, 3 sigma", "15% Bg, 3 sigma", "20% Bg, 3 sigma", "30% Bg, 2 sigma"]
20
+ self.stretch_option_current = StringVar()
21
+ self.stretch_option_current.set(graxpert.prefs.stretch_option)
22
+ self.stretch_option_current.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.STRETCH_OPTION_CHANGED, {"stretch_option": self.stretch_option_current.get()}))
23
+
24
+ self.saturation = tk.DoubleVar()
25
+ self.saturation.set(graxpert.prefs.saturation)
26
+ self.saturation.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.CHANGE_SATURATION_REQUEST, {"saturation": self.saturation.get()}))
27
+
28
+ self.channels_linked = tk.BooleanVar()
29
+ self.channels_linked.set(graxpert.prefs.channels_linked_option)
30
+ self.channels_linked.trace_add("write", lambda a, b, c: eventbus.emit(AppEvents.CHANNELS_LINKED_CHANGED, {"channels_linked": self.channels_linked.get()}))
31
+
32
+ self.create_children()
33
+ self.setup_layout()
34
+ self.place_children()
35
+ self.register_events()
36
+
37
+ # widget setup
38
+ def create_children(self):
39
+ self.label_image_info = CTkLabel(self, text="image info")
40
+ self.label_image_pixel = CTkLabel(self, text="(x, y)")
41
+
42
+ self.stretch_option_frame = CTkFrame(self)
43
+
44
+ self.stretch_options_title = ProcessingStep(self.stretch_option_frame, number=0, indent=2, title=_(" Stretch Options"))
45
+ self.stretch_menu = GraXpertOptionMenu(
46
+ self.stretch_option_frame,
47
+ variable=self.stretch_option_current,
48
+ values=self.stretch_options,
49
+ )
50
+ tooltip.Tooltip(self.stretch_menu, text=tooltip.stretch_text)
51
+ self.saturation_slider = ValueSlider(
52
+ self.stretch_option_frame,
53
+ width=default_label_width,
54
+ variable_name=_("Saturation"),
55
+ variable=self.saturation,
56
+ min_value=0,
57
+ max_value=3,
58
+ precision=1,
59
+ )
60
+ self.channels_linked_switch = GraXpertCheckbox(self.stretch_option_frame, width=default_label_width, text=_("Channels linked"), variable=self.channels_linked)
61
+
62
+ def setup_layout(self):
63
+ self.columnconfigure(0, weight=1)
64
+ self.rowconfigure(0, weight=1)
65
+
66
+ def place_children(self):
67
+ self.stretch_option_frame.grid(column=0, row=0, sticky=tk.NS)
68
+ self.label_image_info.grid(column=0, row=0, padx=padx, sticky=tk.W)
69
+ self.label_image_pixel.grid(column=0, row=0, padx=padx, sticky=tk.E)
70
+
71
+ self.stretch_menu.grid(column=0, row=0, padx=padx, pady=pady, sticky=tk.E)
72
+ self.saturation_slider.grid(column=1, row=0, padx=padx, pady=pady, sticky=tk.EW)
73
+ self.channels_linked_switch.grid(column=2, row=0, padx=padx, pady=pady, sticky=tk.W)
74
+
75
+ def register_events(self):
76
+ eventbus.add_listener(AppEvents.LOAD_IMAGE_END, self.on_load_image_end)
77
+ eventbus.add_listener(UiEvents.MOUSE_MOVED, self.on_mouse_move)
78
+
79
+ # event handling
80
+ def on_load_image_end(self, event):
81
+ self.label_image_info.configure(
82
+ text=f"{graxpert.data_type} : {graxpert.images.get(ImageTypes.Original).img_display.width} x {graxpert.images.get(ImageTypes.Original).img_display.height} {graxpert.images.get(ImageTypes.Original).img_display.mode}"
83
+ )
84
+
85
+ def on_mouse_move(self, event):
86
+ if graxpert.images.get(graxpert.display_type) is None:
87
+ return
88
+
89
+ image_point = graxpert.to_image_point(event["mouse_event"].x, event["mouse_event"].y)
90
+ if len(image_point) != 0:
91
+ text = "x=" + f"{image_point[0]:.2f}" + ",y=" + f"{image_point[1]:.2f} "
92
+ if graxpert.images.get(graxpert.display_type).img_array.shape[2] == 3:
93
+ R, G, B = graxpert.images.get(graxpert.display_type).get_local_median(image_point)
94
+ text = text + "RGB = (" + f"{R:.4f}," + f"{G:.4f}," + f"{B:.4f})"
95
+
96
+ if graxpert.images.get(graxpert.display_type).img_array.shape[2] == 1:
97
+ L = graxpert.images.get(graxpert.display_type).get_local_median(image_point)
98
+ text = text + "L= " + f"{L:.4f}"
99
+
100
+ self.label_image_pixel.configure(text=text)
101
+ else:
102
+ self.label_image_pixel.configure(text="(--, --)")
graxpert/ui/styling.py ADDED
@@ -0,0 +1,23 @@
1
+ import os
2
+ import shutil
3
+ from importlib import resources
4
+
5
+ import customtkinter
6
+
7
+ from graxpert.resource_utils import resource_path, temp_resource_path
8
+ from graxpert.ui_scaling import get_scaling_factor
9
+
10
+
11
+ def style():
12
+ theme_file = "graxpert-dark-blue.json"
13
+
14
+ # Use importlib.resources to get a reliable path to the theme file
15
+ # This works whether the code is run from source or installed via pip/pipx
16
+ with resources.as_file(resources.files('graxpert.theme').joinpath(theme_file)) as theme_path:
17
+ # Now copy the file from the reliably found path
18
+ shutil.copy(theme_path, temp_resource_path(theme_file))
19
+
20
+ customtkinter.set_default_color_theme(temp_resource_path(theme_file))
21
+ customtkinter.set_appearance_mode("dark")
22
+ scaling = get_scaling_factor()
23
+ customtkinter.set_widget_scaling(scaling)
graxpert/ui/tooltip.py ADDED
@@ -0,0 +1,194 @@
1
+ import tkinter as tk
2
+ import tkinter.ttk as ttk
3
+
4
+ from customtkinter import CTkFrame, CTkLabel, CTkToplevel
5
+
6
+ from graxpert.application.app_events import AppEvents
7
+ from graxpert.application.eventbus import eventbus
8
+ from graxpert.localization import _
9
+ from graxpert.ui_scaling import get_scaling_factor
10
+
11
+
12
+ class Tooltip:
13
+ """
14
+ It creates a tooltip for a given widget as the mouse goes on it.
15
+
16
+ see:
17
+
18
+ http://stackoverflow.com/questions/3221956/
19
+ what-is-the-simplest-way-to-make-tooltips-
20
+ in-tkinter/36221216#36221216
21
+
22
+ http://www.daniweb.com/programming/software-development/
23
+ code/484591/a-tooltip-class-for-tkinter
24
+
25
+ - Originally written by vegaseat on 2014.09.09.
26
+
27
+ - Modified to include a delay time by Victor Zaccardo on 2016.03.25.
28
+
29
+ - Modified
30
+ - to correct extreme right and extreme bottom behavior,
31
+ - to stay inside the screen whenever the tooltip might go out on
32
+ the top but still the screen is higher than the tooltip,
33
+ - to use the more flexible mouse positioning,
34
+ - to add customizable background color, padding, waittime and
35
+ wraplength on creation
36
+ by Alberto Vassena on 2016.11.05.
37
+
38
+ Tested on Ubuntu 16.04/16.10, running Python 3.5.2
39
+
40
+ TODO: themes styles support
41
+ """
42
+
43
+ def __init__(self, widget, *, pad=(5, 3, 5, 3), text="widget info", waittime=500, wraplength=250):
44
+ self.waittime = waittime # in miliseconds, originally 500
45
+ self.wraplength = wraplength * get_scaling_factor() # in pixels, originally 180
46
+ self.widget = widget
47
+ self.text = text
48
+ self.widget.bind("<Enter>", self.onEnter)
49
+ self.widget.bind("<Leave>", self.onLeave)
50
+ self.widget.bind("<ButtonPress>", self.onLeave)
51
+ self.pad = pad
52
+ self.id = None
53
+ self.tw = None
54
+ self.enable_tt = True
55
+ eventbus.add_listener(AppEvents.CALCULATE_REQUEST, lambda e: self.disable())
56
+ eventbus.add_listener(AppEvents.DENOISE_REQUEST, lambda e: self.disable())
57
+ eventbus.add_listener(AppEvents.CALCULATE_END, lambda e: self.enable())
58
+ eventbus.add_listener(AppEvents.DENOISE_END, lambda e: self.enable())
59
+
60
+ def onEnter(self, event=None):
61
+ self.schedule()
62
+
63
+ def onLeave(self, event=None):
64
+ self.unschedule()
65
+ self.hide()
66
+
67
+ def schedule(self):
68
+ self.unschedule()
69
+ self.id = self.widget.after(self.waittime, self.show)
70
+
71
+ def unschedule(self):
72
+ id_ = self.id
73
+ self.id = None
74
+ if id_:
75
+ self.widget.after_cancel(id_)
76
+
77
+ def show(self):
78
+
79
+ if not self.enable_tt:
80
+ return
81
+
82
+ def tip_pos_calculator(widget, label, *, tip_delta=(10, 5), pad=(5, 3, 5, 3)):
83
+ w = widget
84
+
85
+ s_width, s_height = w.winfo_screenwidth(), w.winfo_screenheight()
86
+
87
+ width, height = (pad[0] + label.winfo_reqwidth() + pad[2], pad[1] + label.winfo_reqheight() + pad[3])
88
+
89
+ mouse_x, mouse_y = w.winfo_pointerxy()
90
+
91
+ x1, y1 = mouse_x + tip_delta[0], mouse_y + tip_delta[1]
92
+ x2, y2 = x1 + width, y1 + height
93
+
94
+ x_delta = x2 - s_width
95
+ if x_delta < 0:
96
+ x_delta = 0
97
+ y_delta = y2 - s_height
98
+ if y_delta < 0:
99
+ y_delta = 0
100
+
101
+ offscreen = (x_delta, y_delta) != (0, 0)
102
+
103
+ if offscreen:
104
+ if x_delta:
105
+ x1 = mouse_x - tip_delta[0] - width
106
+
107
+ if y_delta:
108
+ y1 = mouse_y - tip_delta[1] - height
109
+
110
+ offscreen_again = y1 < 0 # out on the top
111
+
112
+ if offscreen_again:
113
+ # No further checks will be done.
114
+
115
+ # TIP:
116
+ # A further mod might automagically augment the
117
+ # wraplength when the tooltip is too high to be
118
+ # kept inside the screen.
119
+ y1 = 0
120
+
121
+ return x1, y1
122
+
123
+ pad = self.pad
124
+ widget = self.widget
125
+
126
+ # creates a toplevel window
127
+ self.tw = CTkToplevel(widget)
128
+
129
+ # Leaves only the label and removes the app window
130
+ self.tw.wm_overrideredirect(True)
131
+
132
+ win = CTkFrame(self.tw, border_width=0)
133
+ label = CTkLabel(win, text=self.text, justify=tk.LEFT, wraplength=self.wraplength)
134
+
135
+ label.grid(padx=(pad[0], pad[2]), pady=(pad[1], pad[3]), sticky=tk.NSEW)
136
+ win.grid()
137
+
138
+ x, y = tip_pos_calculator(widget, label)
139
+
140
+ self.tw.wm_geometry("+%d+%d" % (x, y))
141
+
142
+ def hide(self):
143
+ tw = self.tw
144
+ if tw:
145
+ tw.destroy()
146
+ self.tw = None
147
+
148
+ def enable(self):
149
+ self.enable_tt = True
150
+
151
+ def disable(self):
152
+ self.enable_tt = False
153
+ self.hide()
154
+
155
+
156
+ load_text = _("Load your image you would like to correct. \n" "\n" "Supported formats: .tiff, .fits, .png, .jpg \n" "Supported bitdepths: 16 bit integer, 32 bit float")
157
+
158
+ stretch_text = _("Automatically stretch the picture to make gradients more visible. " "The saved pictures are unaffected by the stretch.")
159
+
160
+ reset_text = _("Reset all the chosen background points.")
161
+
162
+ bg_select_text = _("Creates a grid with the specified amount of points per row " "and rejects points below a threshold defined by the tolerance.")
163
+
164
+ bg_tol_text = _("The tolerance adjusts the threshold for rejection of background points " "with automatic background selection")
165
+
166
+ bg_flood_text = _("If enabled, additional grid points are automatically created based on " "1) the luminance of the sample just added and " "2) the grid tolerance slider below.")
167
+
168
+ num_points_text = _("Adjust the number of points per row for the grid created by" " automatic background selection.")
169
+
170
+ interpol_type_text = _("Choose between different interpolation methods.")
171
+
172
+ smoothing_text = _(
173
+ "Adjust the smoothing parameter for the interpolation method. "
174
+ "A too small smoothing parameter may lead to over- and undershooting "
175
+ "inbetween background points, while a too large smoothing parameter "
176
+ "may not be suited for large deviations in gradients."
177
+ )
178
+
179
+ calculate_text = _("Use the specified interpolation method to calculate a background model " "and subtract it from the picture. This may take a while.")
180
+
181
+ deconvolution_type_text = _("Choose between different deconvolution methods.")
182
+ deconvolution_text = _("Use GraXpert's deconvolution AI model to reduce the blur in your image. This may take a while")
183
+ deconvolution_strength_text = _("Determines strength of deconvolution.")
184
+ deconvolution_psfsize_text = _("Informs the AI on how much blur to expect in the image. The right parameters is found when all artifacts disappear.")
185
+
186
+ denoise_text = _("Use GraXpert's denoising AI model to reduce the noise in your image. This may take a while")
187
+ denoise_strength_text = _("Determines strength of denoising.")
188
+ denoise_threshold_text = _("Determines the upper bound up to which pixels are denoised. Pixels above this threshold are not denoised and taken from the original image.")
189
+
190
+ saveas_text = _("Choose the bitdepth of the saved pictures and the file format. " "If you are working with a .fits image the fits header will " "be preserved.")
191
+ saveas_stretched_text = _("Enable to save the stretched image instead of the linear one. The color saturation is not changed.")
192
+ save_pic_text = _("Save the currently selected picture")
193
+
194
+ display_text = _("Switch display between \n" "\n" "Original: Your original picture \n" "Processed: Picture with subtracted background model \n" "Background: The background model")
@@ -0,0 +1,19 @@
1
+ from enum import Enum, auto
2
+
3
+
4
+ class UiEvents(Enum):
5
+ # main ui requests
6
+ RESET_ZOOM_REQUEST = auto()
7
+ # menu requests
8
+ SHOW_MENU_REQUEST = auto()
9
+ # crop
10
+ TURN_ON_CROP_MODE = auto()
11
+ TURN_OFF_CROP_MODE = auto()
12
+ APPLY_CROP_REQUEST = auto()
13
+ # right sidebar requests
14
+ HELP_FRAME_TOGGLED = auto()
15
+ ADVANCED_FRAME_TOGGLED = auto()
16
+ # mouse events
17
+ MOUSE_MOVED = auto()
18
+ # cosmetics
19
+ DISPLAY_START_BADGE_REQUEST = auto()