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
Binary file
File without changes
@@ -0,0 +1,39 @@
1
+ import gettext
2
+ import locale
3
+ import os
4
+ from importlib import resources
5
+
6
+ from appdirs import user_config_dir
7
+
8
+ from graxpert.preferences import load_preferences
9
+ from graxpert.resource_utils import resource_path
10
+
11
+ prefs_file = os.path.join(user_config_dir(appname="GraXpert"), "preferences.json")
12
+ prefs = load_preferences(prefs_file)
13
+
14
+ lang = None
15
+ if prefs.lang is None:
16
+ lang, enc = locale.getlocale()
17
+ if lang is None:
18
+ lang = "en_EN"
19
+ elif lang.startswith("de") or lang.startswith("gsw"):
20
+ lang = "de_DE"
21
+ else:
22
+ lang = "en_EN"
23
+
24
+ else:
25
+ lang = prefs.lang
26
+ if lang == "Deutsch":
27
+ lang = "de_DE"
28
+ else:
29
+ lang = "en_EN"
30
+
31
+
32
+ with resources.as_file(resources.files('graxpert').joinpath('locales')) as localedir:
33
+ global lang_gettext
34
+ lang_gettext = gettext.translation("base", localedir=localedir, languages=[lang], fallback=True)
35
+ lang_gettext.install()
36
+
37
+
38
+ def _(text):
39
+ return lang_gettext.gettext(text)
graxpert/main.py ADDED
@@ -0,0 +1,441 @@
1
+ import os
2
+ import platform
3
+ import sys
4
+
5
+ # ensure sys.stdout and sys.stderr are not None in PyInstaller environments
6
+ if sys.stdout is None:
7
+ sys.stdout = open(os.devnull, "w")
8
+ if sys.stderr is None:
9
+ sys.stderr = open(os.devnull, "w")
10
+ import argparse
11
+ import logging
12
+ import multiprocessing
13
+ import re
14
+ import sys
15
+ import faulthandler
16
+ import types
17
+
18
+ from packaging import version
19
+
20
+ # Must be before all other graxpert imports
21
+ try:
22
+ # A load-time check for key external package dependencies. Crash and warn user.
23
+ import tkinter
24
+ except ImportError:
25
+ # If it fails, print a helpful message and exit.
26
+ print("ERROR: The required 'tkinter' GUI library is not installed.", file=sys.stderr)
27
+ print("\nPlease install it using your system's package manager.", file=sys.stderr)
28
+ print(" For Debian/Ubuntu: sudo apt-get install python3-tk", file=sys.stderr)
29
+ print(" For Fedora: sudo dnf install python3-tkinter", file=sys.stderr)
30
+ print(" For Arch Linux: sudo pacman -S tk", file=sys.stderr)
31
+ print(" For Windows/OS-X: It should be included with your Python installation by default.", file=sys.stderr)
32
+ sys.exit(1) # Exit with error
33
+
34
+
35
+ from graxpert.ai_model_handling import bge_ai_models_dir, denoise_ai_models_dir, deconvolution_object_ai_models_dir, deconvolution_stars_ai_models_dir, list_local_versions, list_remote_versions
36
+ from graxpert.mp_logging import configure_logging
37
+ from graxpert.s3_secrets import bge_bucket_name, denoise_bucket_name, deconvolution_object_bucket_name, deconvolution_stars_bucket_name
38
+ from graxpert.version import release as graxpert_release
39
+ from graxpert.version import version as graxpert_version
40
+ from graxpert.resource_utils import temp_cleanup
41
+
42
+ def collect_available_versions(ai_models_dir, bucket_name):
43
+
44
+ try:
45
+ available_local_versions = sorted(
46
+ [v["version"] for v in list_local_versions(ai_models_dir)],
47
+ key=lambda k: version.parse(k),
48
+ reverse=True,
49
+ )
50
+ except Exception as e:
51
+ available_local_versions = ""
52
+ logging.exception(e)
53
+ try:
54
+ available_remote_versions = sorted(
55
+ [v["version"] for v in list_remote_versions(bucket_name)],
56
+ key=lambda k: version.parse(k),
57
+ reverse=True,
58
+ )
59
+ except Exception as e:
60
+ available_remote_versions = ""
61
+ logging.exception(e)
62
+
63
+ return (available_local_versions, available_remote_versions)
64
+
65
+
66
+ def bge_version_type(arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$")):
67
+ return version_type(bge_ai_models_dir, bge_bucket_name, arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$"))
68
+
69
+
70
+ def denoise_version_type(arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$")):
71
+ return version_type(denoise_ai_models_dir, denoise_bucket_name, arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$"))
72
+
73
+
74
+ def deconv_obj_version_type(arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$")):
75
+ return version_type(deconvolution_object_ai_models_dir, deconvolution_object_bucket_name, arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$"))
76
+
77
+
78
+ def deconv_stellar_version_type(arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$")):
79
+ return version_type(deconvolution_stars_ai_models_dir, deconvolution_stars_bucket_name, arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$"))
80
+
81
+
82
+ def version_type(ai_models_dir, bucket_name, arg_value, pat=re.compile(r"^\d+\.\d+\.\d+$")):
83
+
84
+ available_versions = collect_available_versions(ai_models_dir, bucket_name)
85
+ available_local_versions = available_versions[0]
86
+ available_remote_versions = available_versions[1]
87
+
88
+ if not pat.match(arg_value):
89
+ raise argparse.ArgumentTypeError("invalid version, expected format: n.n.n")
90
+ if arg_value not in available_local_versions and arg_value not in available_remote_versions:
91
+ raise argparse.ArgumentTypeError(
92
+ "provided version neither found locally or remotely; available locally: [{}], available remotely: [{}]".format(
93
+ ", ".join(available_local_versions),
94
+ ", ".join(available_remote_versions),
95
+ )
96
+ )
97
+ if not available_local_versions and not available_remote_versions:
98
+ raise argparse.ArgumentTypeError("no AI versions available locally or remotely")
99
+ return arg_value
100
+
101
+
102
+ def ui_main(open_with_file=None):
103
+ import logging
104
+ import tkinter as tk
105
+ from concurrent.futures import ProcessPoolExecutor
106
+ from datetime import datetime
107
+ from inspect import signature
108
+ from tkinter import messagebox
109
+
110
+ import requests
111
+ from appdirs import user_config_dir
112
+ from customtkinter import CTk
113
+
114
+ from graxpert.application.app import graxpert
115
+ from graxpert.application.app_events import AppEvents
116
+ from graxpert.application.eventbus import eventbus
117
+ from graxpert.localization import _
118
+ from graxpert.mp_logging import initialize_logging, shutdown_logging
119
+ from graxpert.parallel_processing import executor
120
+ from graxpert.preferences import app_state_2_prefs, save_preferences
121
+ from graxpert.resource_utils import resource_photoimage
122
+ from graxpert.ui.application_frame import ApplicationFrame
123
+ from graxpert.ui.styling import style
124
+ from graxpert.ui.ui_events import UiEvents
125
+ from graxpert.version import release, version
126
+
127
+ def on_closing(root: CTk, logging_thread):
128
+ app_state_2_prefs(graxpert.prefs, graxpert.cmd.app_state)
129
+
130
+ prefs_filename = os.path.join(user_config_dir(appname="GraXpert"), "preferences.json")
131
+ save_preferences(prefs_filename, graxpert.prefs)
132
+ try:
133
+ if "cancel_futures" in signature(ProcessPoolExecutor.shutdown).parameters:
134
+ executor.shutdown(cancel_futures=True) # Python > 3.8
135
+ else:
136
+ executor.shutdown() # Python <= 3.8
137
+
138
+ except Exception as e:
139
+ logging.exception("error shutting down ProcessPoolExecutor")
140
+ shutdown_logging(logging_thread)
141
+ root.destroy()
142
+ logging.shutdown()
143
+ sys.exit(0)
144
+
145
+ def check_for_new_version():
146
+ try:
147
+ response = requests.get("https://api.github.com/repos/Steffenhir/GraXpert/releases/latest", timeout=2.5)
148
+ latest_release_date = datetime.strptime(response.json()["created_at"], "%Y-%m-%dT%H:%M:%SZ")
149
+
150
+ response_current = requests.get("https://api.github.com/repos/Steffenhir/GraXpert/releases/tags/" + version, timeout=2.5)
151
+ current_release_date = datetime.strptime(response_current.json()["created_at"], "%Y-%m-%dT%H:%M:%SZ")
152
+ current_is_beta = response_current.json()["prerelease"]
153
+
154
+ if current_is_beta:
155
+ if current_release_date >= latest_release_date:
156
+ messagebox.showinfo(
157
+ title=_("This is a Beta release!"), message=_("Please note that this is a Beta release of GraXpert. You will be notified when a newer official version is available.")
158
+ )
159
+ else:
160
+ messagebox.showinfo(
161
+ title=_("New official release available!"),
162
+ message=_("This Beta version is deprecated. A newer official release of GraXpert is available at") + " https://github.com/Steffenhir/GraXpert/releases/latest",
163
+ )
164
+
165
+ elif latest_release_date > current_release_date:
166
+ messagebox.showinfo(title=_("New version available!"), message=_("A newer version of GraXpert is available at") + " https://github.com/Steffenhir/GraXpert/releases/latest")
167
+ except:
168
+ logging.warning("Could not check for newest version")
169
+
170
+ logging_thread = initialize_logging()
171
+
172
+ style()
173
+ root = CTk()
174
+
175
+ try:
176
+ if "Linux" == platform.system():
177
+ root.attributes("-zoomed", True)
178
+ else:
179
+ root.state("zoomed")
180
+ except Exception as e:
181
+ root.state("normal")
182
+ logging.warning(e, stack_info=True)
183
+
184
+ root.title("GraXpert | Release: '{}' ({})".format(release, version))
185
+ root.iconbitmap()
186
+ root.iconphoto(True, resource_photoimage("Icon.png"))
187
+ # root.option_add("*TkFDialog*foreground", "black")
188
+ root.protocol("WM_DELETE_WINDOW", lambda: on_closing(root, logging_thread))
189
+ root.createcommand("::tk::mac::Quit", lambda: on_closing(root, logging_thread))
190
+ root.minsize(width=800, height=600)
191
+ root.columnconfigure(0, weight=1)
192
+ root.rowconfigure(0, weight=1)
193
+ app = ApplicationFrame(root)
194
+ app.grid(column=0, row=0, sticky=tk.NSEW)
195
+ root.update()
196
+ check_for_new_version()
197
+
198
+ if open_with_file and len(open_with_file) > 0:
199
+ eventbus.emit(AppEvents.LOAD_IMAGE_REQUEST, {"filename": open_with_file})
200
+ else:
201
+ eventbus.emit(UiEvents.DISPLAY_START_BADGE_REQUEST)
202
+
203
+ root.mainloop()
204
+
205
+
206
+ def parse_args():
207
+ available_bge_versions = collect_available_versions(bge_ai_models_dir, bge_bucket_name)
208
+ available_denoise_versions = collect_available_versions(denoise_ai_models_dir, denoise_bucket_name)
209
+ available_deconv_obj_versions = collect_available_versions(deconvolution_object_ai_models_dir, deconvolution_object_bucket_name)
210
+ available_deconv_stellar_versions = collect_available_versions(deconvolution_stars_ai_models_dir, deconvolution_stars_bucket_name)
211
+
212
+ parser = argparse.ArgumentParser(add_help=False)
213
+ parser.add_argument(
214
+ "-cmd",
215
+ "--command",
216
+ required=False,
217
+ default="background-extraction",
218
+ choices=["background-extraction", "denoising", "deconv-obj", "deconv-stellar"],
219
+ type=str,
220
+ help="Choose the image operation to execute: Background Extraction or Denoising or Deconvolution",
221
+ )
222
+ parser.add_argument("filename", type=str, help="Path of the unprocessed image")
223
+ parser.add_argument("-output", "--output", nargs="?", required=False, type=str, help="Filename of the processed image")
224
+ parser.add_argument(
225
+ "-preferences_file",
226
+ "--preferences_file",
227
+ nargs="?",
228
+ required=False,
229
+ default=None,
230
+ type=str,
231
+ help="Allows GraXpert commandline to run all extraction methods based on a preferences file that contains background grid points",
232
+ )
233
+ parser.add_argument("-gpu", "--gpu_acceleration", type=str, choices=["true", "false"], default=None, help="Set to 'false' in order to disable gpu acceleration during AI inference.")
234
+ parser.add_argument("-v", "--version", action="version", version=f"GraXpert version: {graxpert_version} release: {graxpert_release}")
235
+ parser.add_argument("-cli", "--cli", required=False, action="store_true", help="Deprecated (no longer required)")
236
+
237
+ bge_parser = argparse.ArgumentParser("GraXpert Background Extraction", parents=[parser], description="GraXpert, the astronomical background extraction tool")
238
+ bge_parser.add_argument(
239
+ "-ai_version",
240
+ "--ai_version",
241
+ nargs="?",
242
+ required=False,
243
+ default=None,
244
+ type=bge_version_type,
245
+ help='Version of the Background Extraction AI model, default: "latest"; available locally: [{}], available remotely: [{}]'.format(
246
+ ", ".join(available_bge_versions[0]), ", ".join(available_bge_versions[1])
247
+ ),
248
+ )
249
+ bge_parser.add_argument("-correction", "--correction", nargs="?", required=False, default=None, choices=["Subtraction", "Division"], type=str, help="Subtraction or Division")
250
+ bge_parser.add_argument("-smoothing", "--smoothing", nargs="?", required=False, default=None, type=float, help="Strength of smoothing between 0 and 1")
251
+ bge_parser.add_argument("-bg", "--bg", required=False, action="store_true", help="Also save the background model")
252
+
253
+ denoise_parser = argparse.ArgumentParser("GraXpert Denoising", parents=[parser], description="GraXpert, the astronomical denoising tool")
254
+ denoise_parser.add_argument(
255
+ "-ai_version",
256
+ "--ai_version",
257
+ nargs="?",
258
+ required=False,
259
+ default=None,
260
+ type=denoise_version_type,
261
+ help='Version of the Denoising AI model, default: "latest"; available locally: [{}], available remotely: [{}]'.format(
262
+ ", ".join(available_denoise_versions[0]), ", ".join(available_denoise_versions[1])
263
+ ),
264
+ )
265
+ denoise_parser.add_argument(
266
+ "-strength",
267
+ "--denoise_strength",
268
+ nargs="?",
269
+ required=False,
270
+ default=None,
271
+ type=float,
272
+ help='Strength of the desired denoising effect, default: "0.5"',
273
+ )
274
+ denoise_parser.add_argument(
275
+ "-batch_size",
276
+ "--ai_batch_size",
277
+ nargs="?",
278
+ required=False,
279
+ default=None,
280
+ type=int,
281
+ help='Number of image tiles which Graxpert will denoise in parallel. Be careful: increasing this value might result in out-of-memory errors. Valid Range: 1..32, default: "4"',
282
+ )
283
+
284
+ deconv_obj_parser = argparse.ArgumentParser("GraXpert Deconvolution Object", parents=[parser], description="GraXpert, the astronomical deconvolution tool")
285
+ deconv_obj_parser.add_argument(
286
+ "-ai_version",
287
+ "--ai_version",
288
+ nargs="?",
289
+ required=False,
290
+ default=None,
291
+ type=deconv_obj_version_type,
292
+ help='Version of the Deconvolution Obj AI model, default: "latest"; available locally: [{}], available remotely: [{}]'.format(
293
+ ", ".join(available_deconv_obj_versions[0]), ", ".join(available_deconv_obj_versions[1])
294
+ ),
295
+ )
296
+ deconv_obj_parser.add_argument(
297
+ "-strength",
298
+ "--deconvolution_strength",
299
+ nargs="?",
300
+ required=False,
301
+ default=None,
302
+ type=float,
303
+ help='Strength of the desired deconvolution effect, default: "0.5"',
304
+ )
305
+ deconv_obj_parser.add_argument(
306
+ "-psfsize",
307
+ "--deconvolution_psfsize",
308
+ nargs="?",
309
+ required=False,
310
+ default=None,
311
+ type=float,
312
+ help='Size of the seeing you want to deconvolve, default: "0.3"',
313
+ )
314
+ deconv_obj_parser.add_argument(
315
+ "-batch_size",
316
+ "--ai_batch_size",
317
+ nargs="?",
318
+ required=False,
319
+ default=None,
320
+ type=int,
321
+ help='Number of image tiles which Graxpert will denoise in parallel. Be careful: increasing this value might result in out-of-memory errors. Valid Range: 1..32, default: "4"',
322
+ )
323
+
324
+ deconv_stellar_parser = argparse.ArgumentParser("GraXpert Deconvolution Stellar", parents=[parser], description="GraXpert, the astronomical denoising tool")
325
+ deconv_stellar_parser.add_argument(
326
+ "-ai_version",
327
+ "--ai_version",
328
+ nargs="?",
329
+ required=False,
330
+ default=None,
331
+ type=deconv_stellar_version_type,
332
+ help='Version of the Deconvolution Stellar AI model, default: "latest"; available locally: [{}], available remotely: [{}]'.format(
333
+ ", ".join(available_deconv_stellar_versions[0]), ", ".join(available_deconv_stellar_versions[1])
334
+ ),
335
+ )
336
+ deconv_stellar_parser.add_argument(
337
+ "-strength",
338
+ "--deconvolution_strength",
339
+ nargs="?",
340
+ required=False,
341
+ default=None,
342
+ type=float,
343
+ help='Strength of the desired deconvolution effect, default: "0.5"',
344
+ )
345
+ deconv_stellar_parser.add_argument(
346
+ "-psfsize",
347
+ "--deconvolution_psfsize",
348
+ nargs="?",
349
+ required=False,
350
+ default=None,
351
+ type=float,
352
+ help='Size of the seeing you want to deconvolve, default: "0.3"',
353
+ )
354
+ deconv_stellar_parser.add_argument(
355
+ "-batch_size",
356
+ "--ai_batch_size",
357
+ nargs="?",
358
+ required=False,
359
+ default=None,
360
+ type=int,
361
+ help='Number of image tiles which Graxpert will denoise in parallel. Be careful: increasing this value might result in out-of-memory errors. Valid Range: 1..32, default: "4"',
362
+ )
363
+
364
+ if "-h" in sys.argv or "--help" in sys.argv:
365
+ if "background-extraction" in sys.argv:
366
+ bge_parser.print_help()
367
+ elif "denoising" in sys.argv:
368
+ denoise_parser.print_help()
369
+ elif "deconv-obj" in sys.argv:
370
+ deconv_obj_parser.print_help()
371
+ elif "deconv-stellar" in sys.argv:
372
+ deconv_stellar_parser.print_help()
373
+ else:
374
+ parser.print_help()
375
+ sys.exit(0)
376
+
377
+ args, extras = parser.parse_known_args()
378
+
379
+ if args.command == "background-extraction":
380
+ args = bge_parser.parse_args()
381
+ elif args.command == "deconv-obj":
382
+ args = deconv_obj_parser.parse_args()
383
+ elif args.command == "deconv-stellar":
384
+ args = deconv_stellar_parser.parse_args()
385
+ elif args.command == "denoising":
386
+ args = denoise_parser.parse_args()
387
+
388
+ return args
389
+
390
+ def main():
391
+ """Note: this is entered directly via the entry_point definition in setup.py or called from below"""
392
+
393
+ multiprocessing.freeze_support()
394
+ faulthandler.enable(sys.__stderr__)
395
+
396
+ try:
397
+ # listing available versions might be slow, so only do it if we have command line args
398
+ if len(sys.argv) > 1:
399
+ args = parse_args()
400
+ else:
401
+ # Dummy noarg defs
402
+ args = types.SimpleNamespace(command=None, filename=None)
403
+
404
+ # Note: we wait to setup logging until after parsing args, so that --help response doesn't get log framing
405
+ configure_logging()
406
+
407
+ if args.command == "background-extraction":
408
+ from graxpert.cmdline_tools import BGECmdlineTool
409
+
410
+ logging.info(f"Starting GraXpert CLI, Background-Extraction, version: {graxpert_version} release: {graxpert_release}")
411
+ clt = BGECmdlineTool(args)
412
+ clt.execute()
413
+ elif args.command == "denoising":
414
+ from graxpert.cmdline_tools import DenoiseCmdlineTool
415
+
416
+ logging.info(f"Starting GraXpert CLI, Denoising, version: {graxpert_version} release: {graxpert_release}")
417
+ clt = DenoiseCmdlineTool(args)
418
+ clt.execute()
419
+ elif args.command == "deconv-obj":
420
+ from graxpert.cmdline_tools import DeconvObjCmdlineTool
421
+
422
+ logging.info(f"Starting GraXpert CLI, Deconvolution Obj, version: {graxpert_version} release: {graxpert_release}")
423
+ clt = DeconvObjCmdlineTool(args)
424
+ clt.execute()
425
+ elif args.command == "deconv-stellar":
426
+ from graxpert.cmdline_tools import DeconvStellarCmdlineTool
427
+
428
+ logging.info(f"Starting GraXpert CLI, Deconvolution Stellar, version: {graxpert_version} release: {graxpert_release}")
429
+ clt = DeconvStellarCmdlineTool(args)
430
+ clt.execute()
431
+ else:
432
+ logging.info(f"Starting GraXpert UI, version: {graxpert_version} release: {graxpert_release}")
433
+ ui_main(args.filename)
434
+ finally:
435
+ temp_cleanup()
436
+ logging.shutdown()
437
+
438
+
439
+ if __name__ == "__main__":
440
+ main()
441
+
graxpert/mp_logging.py ADDED
@@ -0,0 +1,97 @@
1
+ import logging
2
+ import logging.handlers
3
+ import multiprocessing
4
+ import os
5
+ import sys
6
+ import threading
7
+
8
+ from appdirs import user_log_dir
9
+
10
+
11
+ class StreamToLogger(object):
12
+ """
13
+ Fake file-like stream object that redirects writes to a logger instance.
14
+ """
15
+
16
+ def __init__(self, logger, log_level=logging.INFO):
17
+ self.logger = logger
18
+ self.log_level = log_level
19
+ self.linebuf = ""
20
+ self.line_complete = False
21
+
22
+ def write(self, buf):
23
+ self.linebuf += buf
24
+ if self.linebuf.count("\n") > 0:
25
+ self.line_complete = True
26
+ self.flush()
27
+
28
+ def flush(self):
29
+ if self.line_complete:
30
+ for s in self.linebuf.splitlines():
31
+ self.logger.log(self.log_level, s.rstrip())
32
+ self.linebuf = ""
33
+ self.line_complete = False
34
+
35
+
36
+ # cf. https://docs.python.org/3/howto/logging-cookbook.html#using-concurrent-futures-processpoolexecutor
37
+
38
+ logging_queue = None
39
+ logfile_name = os.path.join(user_log_dir(appname="GraXpert"), "graxpert.log")
40
+
41
+
42
+ def get_logging_queue():
43
+ global logging_queue
44
+ if logging_queue is None:
45
+ logging_queue = multiprocessing.Manager().Queue(-1)
46
+ return logging_queue
47
+
48
+
49
+ def configure_logging():
50
+ os.makedirs(os.path.dirname(logfile_name), exist_ok=True)
51
+ root = logging.getLogger()
52
+ root.setLevel(logging.INFO)
53
+ h = logging.handlers.RotatingFileHandler(logfile_name, "a", maxBytes=1000000, backupCount=5, encoding="utf-8")
54
+ f = logging.Formatter("%(asctime)s %(processName)-10s %(name)s %(levelname)-8s %(message)s")
55
+ h.setFormatter(f)
56
+ root.handlers = []
57
+ root.addHandler(h)
58
+
59
+ h2 = logging.StreamHandler()
60
+ h2.setFormatter(f)
61
+ root.addHandler(h2)
62
+
63
+ # stdout_logger = logging.getLogger("STDOUT")
64
+ sl = StreamToLogger(root, logging.INFO)
65
+ sys.stdout = sl
66
+
67
+ # stderr_logger = logging.getLogger("STDERR")
68
+ sl = StreamToLogger(root, logging.ERROR)
69
+ sys.stderr = sl
70
+
71
+
72
+ def worker_configurer(queue):
73
+ h = logging.handlers.QueueHandler(queue) # Just the one handler needed
74
+ root = logging.getLogger()
75
+ root.addHandler(h)
76
+ # send all messages, for demo; no other level or filter logic applied.
77
+ root.setLevel(logging.INFO)
78
+
79
+
80
+ def logger_thread(queue):
81
+ while True:
82
+ record = queue.get()
83
+ if record is None:
84
+ break
85
+ logger = logging.getLogger(record.name)
86
+ logger.handle(record)
87
+
88
+
89
+ def initialize_logging():
90
+ logging_thread = threading.Thread(target=logger_thread, args=(get_logging_queue(),), daemon=True)
91
+ logging_thread.start()
92
+ return logging_thread
93
+
94
+
95
+ def shutdown_logging(logging_thread):
96
+ get_logging_queue().put(None)
97
+ logging_thread.join()
@@ -0,0 +1,3 @@
1
+ from concurrent.futures import ProcessPoolExecutor
2
+
3
+ executor = ProcessPoolExecutor(max_workers=9)