graxpert 3.2.0a0.dev7__tar.gz → 3.2.0a1__tar.gz

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 (85) hide show
  1. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/PKG-INFO +6 -5
  2. graxpert-3.2.0a1/graxpert/__init__.py +3 -0
  3. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ai_model_handling.py +41 -2
  4. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/application/app.py +18 -11
  5. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/cmdline_tools.py +63 -110
  6. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/main.py +82 -30
  7. graxpert-3.2.0a1/graxpert/version.py +2 -0
  8. graxpert-3.2.0a1/graxpert/version.py.bak +2 -0
  9. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/PKG-INFO +6 -5
  10. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/requires.txt +4 -4
  11. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/pyproject.toml +2 -2
  12. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/requirements.txt +4 -4
  13. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/setup.py +6 -3
  14. graxpert-3.2.0a0.dev7/graxpert/ui/__init__.py +0 -1
  15. graxpert-3.2.0a0.dev7/graxpert/version.py +0 -2
  16. graxpert-3.2.0a0.dev7/graxpert/version.py.bak +0 -2
  17. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/MANIFEST.in +0 -0
  18. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/README.md +0 -0
  19. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/AstroImageRepository.py +0 -0
  20. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/app_state.py +0 -0
  21. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/application/__init__.py +0 -0
  22. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/application/app_events.py +0 -0
  23. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/application/eventbus.py +0 -0
  24. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/astroimage.py +0 -0
  25. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/background_extraction.py +0 -0
  26. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/background_flood_selection.py +0 -0
  27. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/background_grid_selection.py +0 -0
  28. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/commands.py +0 -0
  29. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/deconvolution.py +0 -0
  30. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/denoising.py +0 -0
  31. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/grid_utils.py +0 -0
  32. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/GraXpert_LOGO_Hauptvariante.png +0 -0
  33. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/Icon.png +0 -0
  34. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/LDN1235_original.jpg +0 -0
  35. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/LDN1235_processed.jpg +0 -0
  36. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/__init__.py +0 -0
  37. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/gfx_numbers.png +0 -0
  38. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/gfx_numbers.svg +0 -0
  39. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/graXpert_Startbadge_Umbriel.png +0 -0
  40. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/img/hourglass.png +0 -0
  41. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/locales/__init__.py +0 -0
  42. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/locales/base.pot +0 -0
  43. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/locales/de_DE/LC_MESSAGES/base.mo +0 -0
  44. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/locales/de_DE/LC_MESSAGES/base.po +0 -0
  45. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/localization.py +0 -0
  46. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/mp_logging.py +0 -0
  47. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/parallel_processing.py +0 -0
  48. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/preferences.py +0 -0
  49. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/radialbasisinterpolation.py +0 -0
  50. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/resource_utils.py +0 -0
  51. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/s3_secrets.py +0 -0
  52. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/skyall.py +0 -0
  53. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/stretch.py +0 -0
  54. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/theme/__init__.py +0 -0
  55. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/theme/graxpert-dark-blue.json +0 -0
  56. {graxpert-3.2.0a0.dev7/graxpert → graxpert-3.2.0a1/graxpert/ui}/__init__.py +0 -0
  57. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/application_frame.py +0 -0
  58. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/canvas.py +0 -0
  59. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/left_menu.py +0 -0
  60. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/loadingframe.py +0 -0
  61. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/right_menu.py +0 -0
  62. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/statusbar.py +0 -0
  63. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/styling.py +0 -0
  64. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/tooltip.py +0 -0
  65. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/ui_events.py +0 -0
  66. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui/widgets.py +0 -0
  67. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert/ui_scaling.py +0 -0
  68. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/SOURCES.txt +0 -0
  69. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/dependency_links.txt +0 -0
  70. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/entry_points.txt +0 -0
  71. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/graxpert.egg-info/top_level.txt +0 -0
  72. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/Icon.icns +0 -0
  73. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/Icon.ico +0 -0
  74. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/Icon.png +0 -0
  75. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/NGC7000_original.jpg +0 -0
  76. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/NGC7000_processed.jpg +0 -0
  77. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Ariel.png +0 -0
  78. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Ariel.svg +0 -0
  79. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Dione.png +0 -0
  80. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Dione.svg +0 -0
  81. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Tethys.png +0 -0
  82. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Tethys.svg +0 -0
  83. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Umbriel.png +0 -0
  84. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/img/graXpert_Startbadge_Umbriel.svg +0 -0
  85. {graxpert-3.2.0a0.dev7 → graxpert-3.2.0a1}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graxpert
3
- Version: 3.2.0a0.dev7
3
+ Version: 3.2.0a1
4
4
  Summary: GraXpert is an astronomical image processing program for extracting and removing gradients in the background of your astrophotos
5
5
  Home-page: https://graxpert.com
6
6
  Author: GraXpert Development Team
@@ -15,6 +15,7 @@ Classifier: Topic :: Scientific/Engineering :: Image Processing
15
15
  Classifier: Topic :: Multimedia :: Graphics :: Editors
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Operating System :: Microsoft :: Windows
19
20
  Classifier: Operating System :: MacOS
20
21
  Classifier: Operating System :: POSIX :: Linux
@@ -28,17 +29,17 @@ Description-Content-Type: text/markdown
28
29
  Requires-Dist: ml-dtypes>=0.5.3
29
30
  Requires-Dist: pillow>=11.3.0
30
31
  Requires-Dist: appdirs
31
- Requires-Dist: astropy>=6.1.7
32
+ Requires-Dist: astropy>=6.0.1
32
33
  Requires-Dist: astropy-iers-data>=0.2025.9.1.0.42.11
33
34
  Requires-Dist: customtkinter>=5.2.2
34
35
  Requires-Dist: imageio>=2.37.0
35
36
  Requires-Dist: minio>=7.2.16
36
- Requires-Dist: numpy==2.2.6
37
+ Requires-Dist: numpy<=2.2.6,>=2.0.2
37
38
  Requires-Dist: opencv-python-headless==4.11.0.86
38
39
  Requires-Dist: PyKrige>=1.7.2
39
40
  Requires-Dist: requests>=2.32.5
40
- Requires-Dist: scikit-image>=0.25.2
41
- Requires-Dist: scipy>=1.15.3
41
+ Requires-Dist: scikit-image>=0.24.0
42
+ Requires-Dist: scipy>=1.13.1
42
43
  Requires-Dist: xisf>=0.9.5
43
44
  Provides-Extra: cuda
44
45
  Requires-Dist: onnxruntime-gpu[cuda,cudnn]==1.22.0; extra == "cuda"
@@ -0,0 +1,3 @@
1
+ from graxpert.main import api_run
2
+
3
+ __all__ = ['api_run']
@@ -4,6 +4,7 @@ import sys
4
4
  import re
5
5
  import shutil
6
6
  import zipfile
7
+ from multiprocessing import Process, Queue
7
8
 
8
9
  from appdirs import user_data_dir
9
10
  from minio import Minio
@@ -214,6 +215,33 @@ def get_execution_providers_ordered(gpu_acceleration=True):
214
215
  "{}".format(e))
215
216
  sys.exit(1)
216
217
 
218
+
219
+ def run_in_process(fn: callable):
220
+ """Run a function in a separate process and return the result or raise an exception."""
221
+
222
+ q = Queue()
223
+
224
+ # Create a closure to call fn and put the result in the queue
225
+ def wrapped():
226
+ try:
227
+ r = fn()
228
+ except Exception as e:
229
+ r = e # return the exception to the main process
230
+ q.put(r)
231
+
232
+ p = Process(target=wrapped, name="ai-worker")
233
+ p.start()
234
+ p.join()
235
+
236
+ if p.exitcode != 0:
237
+ raise RuntimeError(f"worker process crashed with exit code {p.exitcode}.")
238
+
239
+ result = q.get()
240
+ if isinstance(result, Exception):
241
+ raise result
242
+ return result
243
+
244
+
217
245
  # Provides a mutable session context that can swap out different sessions if needed
218
246
  class SessionContext:
219
247
  model_name: str
@@ -232,13 +260,24 @@ class SessionContext:
232
260
  logging.info(f"Used inference providers for {model_name}: {self.session.get_providers()}")
233
261
 
234
262
 
263
+ def __run_low(self, model_args: dict, gpu_acceleration: bool = True) -> any:
264
+ """Run the ONNX model using the specified execution provider."""
265
+
266
+ # if we are using a GPU the ONNX runtimes might crash due to native bugs (ROCm)
267
+ # so run in a separate process just in case
268
+ fn = lambda: self.session.run(None, model_args)
269
+ if gpu_acceleration:
270
+ return run_in_process(fn)
271
+ else:
272
+ return fn()
273
+
235
274
  def run(self, model_args: dict) -> any:
236
275
  """Run the ONNX model using the specified execution provider."""
237
276
 
238
277
  providers = self.session.get_providers()
239
278
  gpu_acceleration = len(providers) > 1 or providers[0] != "CPUExecutionProvider"
240
279
  try:
241
- result = self.session.run(None, model_args)
280
+ result = self.__run_low(model_args, gpu_acceleration)
242
281
  except Exception as e:
243
282
  if not gpu_acceleration:
244
283
  raise # Rethrow, the failure occurred with the regular CPU model also - show error dialog
@@ -246,7 +285,7 @@ class SessionContext:
246
285
  logging.warning(f"Error running model, falling back to CPU only: {e}")
247
286
  import onnxruntime as ort
248
287
  self.session = ort.InferenceSession(self.model_name, providers=get_execution_providers_ordered(False))
249
- result = self.session.run(None, model_args)
288
+ result = self.__run_low(model_args, False)
250
289
 
251
290
  # all graxpert results are in the first array index
252
291
  return result[0]
@@ -32,6 +32,10 @@ from graxpert.stretch import StretchParameters, stretch_all
32
32
  from graxpert.ui.loadingframe import DynamicProgressThread
33
33
 
34
34
 
35
+ def callback(p):
36
+ eventbus.emit(AppEvents.AI_DOWNLOAD_PROGRESS, {"progress": p})
37
+
38
+
35
39
  class GraXpert:
36
40
 
37
41
  def __init__(self):
@@ -674,8 +678,20 @@ class GraXpert:
674
678
  else:
675
679
  eventbus.emit(AppEvents.AI_DOWNLOAD_BEGIN)
676
680
 
677
- def callback(p):
678
- eventbus.emit(AppEvents.AI_DOWNLOAD_PROGRESS, {"progress": p})
681
+ download_version(bge_ai_models_dir, bge_bucket_name, self.prefs.bge_ai_version, progress=callback)
682
+ eventbus.emit(AppEvents.AI_DOWNLOAD_END)
683
+ return True
684
+
685
+ def validate_bge_ai_installation(self):
686
+ if self.prefs.bge_ai_version is None or self.prefs.bge_ai_version == "None":
687
+ messagebox.showerror("Error", _("No Background Extraction AI-Model selected. Please select one from the Advanced panel on the right."))
688
+ return False
689
+
690
+ if not validate_local_version(bge_ai_models_dir, self.prefs.bge_ai_version):
691
+ if not messagebox.askyesno(_("Install AI-Model?"), _("Selected Background Extraction AI-Model is not installed. Should I download it now?")):
692
+ return False
693
+ else:
694
+ eventbus.emit(AppEvents.AI_DOWNLOAD_BEGIN)
679
695
 
680
696
  download_version(bge_ai_models_dir, bge_bucket_name, self.prefs.bge_ai_version, progress=callback)
681
697
  eventbus.emit(AppEvents.AI_DOWNLOAD_END)
@@ -695,9 +711,6 @@ class GraXpert:
695
711
  else:
696
712
  eventbus.emit(AppEvents.AI_DOWNLOAD_BEGIN)
697
713
 
698
- def callback(p):
699
- eventbus.emit(AppEvents.AI_DOWNLOAD_PROGRESS, {"progress": p})
700
-
701
714
  download_version(deconvolution_object_ai_models_dir, deconvolution_object_bucket_name, self.prefs.deconvolution_object_ai_version, progress=callback)
702
715
  eventbus.emit(AppEvents.AI_DOWNLOAD_END)
703
716
  return True
@@ -712,9 +725,6 @@ class GraXpert:
712
725
  else:
713
726
  eventbus.emit(AppEvents.AI_DOWNLOAD_BEGIN)
714
727
 
715
- def callback(p):
716
- eventbus.emit(AppEvents.AI_DOWNLOAD_PROGRESS, {"progress": p})
717
-
718
728
  download_version(deconvolution_stars_ai_models_dir, deconvolution_stars_bucket_name, self.prefs.deconvolution_stars_ai_version, progress=callback)
719
729
  eventbus.emit(AppEvents.AI_DOWNLOAD_END)
720
730
  return True
@@ -730,9 +740,6 @@ class GraXpert:
730
740
  else:
731
741
  eventbus.emit(AppEvents.AI_DOWNLOAD_BEGIN)
732
742
 
733
- def callback(p):
734
- eventbus.emit(AppEvents.AI_DOWNLOAD_PROGRESS, {"progress": p})
735
-
736
743
  download_version(denoise_ai_models_dir, denoise_bucket_name, self.prefs.denoise_ai_version, progress=callback)
737
744
  eventbus.emit(AppEvents.AI_DOWNLOAD_END)
738
745
  return True
@@ -49,7 +49,9 @@ class CmdlineToolBase:
49
49
  def get_save_path(self):
50
50
  if self.args.output is not None:
51
51
  base_path = os.path.dirname(self.args.filename)
52
- output_file_name = self.args.output + self.get_output_file_ending()
52
+ output_file_name = self.args.output
53
+ if not os.path.splitext(output_file_name)[1]: # if output file already has an extension, just use that
54
+ output_file_name += self.get_output_file_ending()
53
55
  return os.path.join(base_path, output_file_name)
54
56
  else:
55
57
  return os.path.splitext(self.args.filename)[0] + "_GraXpert" + self.get_output_file_ending()
@@ -59,7 +61,7 @@ class BGECmdlineTool(CmdlineToolBase):
59
61
  def __init__(self, args):
60
62
  super().__init__(args)
61
63
 
62
- def execute(self):
64
+ def execute(self, json_prefs: dict = {}):
63
65
  astro_Image = AstroImage(do_update_display=False)
64
66
  astro_Image.set_from_file(self.args.filename, None, None)
65
67
 
@@ -71,43 +73,30 @@ class BGECmdlineTool(CmdlineToolBase):
71
73
 
72
74
  downscale_factor = 1
73
75
 
74
- if self.args.preferences_file is not None:
75
- preferences = Prefs()
76
- preferences.interpol_type_option = "AI"
77
- try:
78
- preferences_file = os.path.abspath(self.args.preferences_file)
79
- if os.path.isfile(preferences_file):
80
- with open(preferences_file, "r") as f:
81
- json_prefs = json.load(f)
82
- if "background_points" in json_prefs:
83
- preferences.background_points = json_prefs["background_points"]
84
- if "RBF_kernel" in json_prefs:
85
- preferences.RBF_kernel = json_prefs["RBF_kernel"]
86
- if "interpol_type_option" in json_prefs:
87
- preferences.interpol_type_option = json_prefs["interpol_type_option"]
88
- if "smoothing_option" in json_prefs:
89
- preferences.smoothing_option = json_prefs["smoothing_option"]
90
- if "sample_size" in json_prefs:
91
- preferences.sample_size = json_prefs["sample_size"]
92
- if "spline_order" in json_prefs:
93
- preferences.spline_order = json_prefs["spline_order"]
94
- if "corr_type" in json_prefs:
95
- preferences.corr_type = json_prefs["corr_type"]
96
- if "ai_version" in json_prefs:
97
- preferences.ai_version = json_prefs["ai_version"]
98
- if "ai_gpu_acceleration" in json_prefs:
99
- preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
100
-
101
- if preferences.interpol_type_option == "Kriging" or preferences.interpol_type_option == "RBF":
102
- downscale_factor = 4
103
-
104
- except Exception as e:
105
- logging.exception(e)
106
- logging.shutdown()
107
- sys.exit(1)
108
- else:
109
- preferences = Prefs()
110
- preferences.interpol_type_option = "AI"
76
+ preferences = Prefs()
77
+ preferences.interpol_type_option = "AI"
78
+
79
+ if "background_points" in json_prefs:
80
+ preferences.background_points = json_prefs["background_points"]
81
+ if "RBF_kernel" in json_prefs:
82
+ preferences.RBF_kernel = json_prefs["RBF_kernel"]
83
+ if "interpol_type_option" in json_prefs:
84
+ preferences.interpol_type_option = json_prefs["interpol_type_option"]
85
+ if "smoothing_option" in json_prefs:
86
+ preferences.smoothing_option = json_prefs["smoothing_option"]
87
+ if "sample_size" in json_prefs:
88
+ preferences.sample_size = json_prefs["sample_size"]
89
+ if "spline_order" in json_prefs:
90
+ preferences.spline_order = json_prefs["spline_order"]
91
+ if "corr_type" in json_prefs:
92
+ preferences.corr_type = json_prefs["corr_type"]
93
+ if "ai_version" in json_prefs:
94
+ preferences.bge_ai_version = json_prefs["ai_version"]
95
+ if "ai_gpu_acceleration" in json_prefs:
96
+ preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
97
+
98
+ if preferences.interpol_type_option == "Kriging" or preferences.interpol_type_option == "RBF":
99
+ downscale_factor = 4
111
100
 
112
101
  if self.args.smoothing is not None:
113
102
  preferences.smoothing_option = self.args.smoothing
@@ -220,7 +209,7 @@ class DenoiseCmdlineTool(CmdlineToolBase):
220
209
  super().__init__(args)
221
210
  self.args = args
222
211
 
223
- def execute(self):
212
+ def execute(self, json_prefs: dict = {}):
224
213
  astro_Image = AstroImage(do_update_display=False)
225
214
  astro_Image.set_from_file(self.args.filename, None, None)
226
215
 
@@ -228,28 +217,16 @@ class DenoiseCmdlineTool(CmdlineToolBase):
228
217
 
229
218
  processed_Astro_Image.fits_header = astro_Image.fits_header
230
219
 
231
- if self.args.preferences_file is not None:
232
- preferences = Prefs()
233
- try:
234
- preferences_file = os.path.abspath(self.args.preferences_file)
235
- if os.path.isfile(preferences_file):
236
- with open(preferences_file, "r") as f:
237
- json_prefs = json.load(f)
238
- if "ai_version" in json_prefs:
239
- preferences.ai_version = json_prefs["ai_version"]
240
- if "denoise_strength" in json_prefs:
241
- preferences.denoise_strength = json_prefs["denoise_strength"]
242
- if "ai_batch_size" in json_prefs:
243
- preferences.ai_batch_size = json_prefs["ai_batch_size"]
244
- if "ai_gpu_acceleration" in json_prefs:
245
- preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
220
+ preferences = Prefs()
246
221
 
247
- except Exception as e:
248
- logging.exception(e)
249
- logging.shutdown()
250
- sys.exit(1)
251
- else:
252
- preferences = Prefs()
222
+ if "ai_version" in json_prefs:
223
+ preferences.ai_version = json_prefs["ai_version"]
224
+ if "denoise_strength" in json_prefs:
225
+ preferences.denoise_strength = json_prefs["denoise_strength"]
226
+ if "ai_batch_size" in json_prefs:
227
+ preferences.ai_batch_size = json_prefs["ai_batch_size"]
228
+ if "ai_gpu_acceleration" in json_prefs:
229
+ preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
253
230
 
254
231
  if self.args.denoise_strength is not None:
255
232
  preferences.denoise_strength = self.args.denoise_strength
@@ -320,7 +297,7 @@ class DeconvObjCmdlineTool(CmdlineToolBase):
320
297
  super().__init__(args)
321
298
  self.args = args
322
299
 
323
- def execute(self):
300
+ def execute(self, json_prefs: dict = {}):
324
301
  astro_Image = AstroImage(do_update_display=False)
325
302
  astro_Image.set_from_file(self.args.filename, None, None)
326
303
 
@@ -328,30 +305,18 @@ class DeconvObjCmdlineTool(CmdlineToolBase):
328
305
 
329
306
  processed_Astro_Image.fits_header = astro_Image.fits_header
330
307
 
331
- if self.args.preferences_file is not None:
332
- preferences = Prefs()
333
- try:
334
- preferences_file = os.path.abspath(self.args.preferences_file)
335
- if os.path.isfile(preferences_file):
336
- with open(preferences_file, "r") as f:
337
- json_prefs = json.load(f)
338
- if "ai_version" in json_prefs:
339
- preferences.ai_version = json_prefs["ai_version"]
340
- if "deconvolution_strength" in json_prefs:
341
- preferences.deconvolution_strength = json_prefs["deconvolution_strength"]
342
- if "deconvolution_psfsize" in json_prefs:
343
- preferences.deconvolution_psfsize = json_prefs["deconvolution_psfsize"]
344
- if "ai_batch_size" in json_prefs:
345
- preferences.ai_batch_size = json_prefs["ai_batch_size"]
346
- if "ai_gpu_acceleration" in json_prefs:
347
- preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
308
+ preferences = Prefs()
348
309
 
349
- except Exception as e:
350
- logging.exception(e)
351
- logging.shutdown()
352
- sys.exit(1)
353
- else:
354
- preferences = Prefs()
310
+ if "ai_version" in json_prefs:
311
+ preferences.ai_version = json_prefs["ai_version"]
312
+ if "deconvolution_strength" in json_prefs:
313
+ preferences.deconvolution_strength = json_prefs["deconvolution_strength"]
314
+ if "deconvolution_psfsize" in json_prefs:
315
+ preferences.deconvolution_psfsize = json_prefs["deconvolution_psfsize"]
316
+ if "ai_batch_size" in json_prefs:
317
+ preferences.ai_batch_size = json_prefs["ai_batch_size"]
318
+ if "ai_gpu_acceleration" in json_prefs:
319
+ preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
355
320
 
356
321
  if self.args.deconvolution_strength is not None:
357
322
  preferences.deconvolution_strength = self.args.deconvolution_strength
@@ -436,7 +401,7 @@ class DeconvStellarCmdlineTool(CmdlineToolBase):
436
401
  super().__init__(args)
437
402
  self.args = args
438
403
 
439
- def execute(self):
404
+ def execute(self, json_prefs: dict = {}):
440
405
  astro_Image = AstroImage(do_update_display=False)
441
406
  astro_Image.set_from_file(self.args.filename, None, None)
442
407
 
@@ -444,30 +409,18 @@ class DeconvStellarCmdlineTool(CmdlineToolBase):
444
409
 
445
410
  processed_Astro_Image.fits_header = astro_Image.fits_header
446
411
 
447
- if self.args.preferences_file is not None:
448
- preferences = Prefs()
449
- try:
450
- preferences_file = os.path.abspath(self.args.preferences_file)
451
- if os.path.isfile(preferences_file):
452
- with open(preferences_file, "r") as f:
453
- json_prefs = json.load(f)
454
- if "ai_version" in json_prefs:
455
- preferences.ai_version = json_prefs["ai_version"]
456
- if "deconvolution_strength" in json_prefs:
457
- preferences.deconvolution_strength = json_prefs["deconvolution_strength"]
458
- if "deconvolution_psfsize" in json_prefs:
459
- preferences.deconvolution_psfsize = json_prefs["deconvolution_psfsize"]
460
- if "ai_batch_size" in json_prefs:
461
- preferences.ai_batch_size = json_prefs["ai_batch_size"]
462
- if "ai_gpu_acceleration" in json_prefs:
463
- preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
464
-
465
- except Exception as e:
466
- logging.exception(e)
467
- logging.shutdown()
468
- sys.exit(1)
469
- else:
470
- preferences = Prefs()
412
+ preferences = Prefs()
413
+
414
+ if "ai_version" in json_prefs:
415
+ preferences.ai_version = json_prefs["ai_version"]
416
+ if "deconvolution_strength" in json_prefs:
417
+ preferences.deconvolution_strength = json_prefs["deconvolution_strength"]
418
+ if "deconvolution_psfsize" in json_prefs:
419
+ preferences.deconvolution_psfsize = json_prefs["deconvolution_psfsize"]
420
+ if "ai_batch_size" in json_prefs:
421
+ preferences.ai_batch_size = json_prefs["ai_batch_size"]
422
+ if "ai_gpu_acceleration" in json_prefs:
423
+ preferences.ai_gpu_acceleration = json_prefs["ai_gpu_acceleration"]
471
424
 
472
425
  if self.args.deconvolution_strength is not None:
473
426
  preferences.deconvolution_strength = self.args.deconvolution_strength
@@ -1,3 +1,4 @@
1
+ import json
1
2
  import os
2
3
  import platform
3
4
  import sys
@@ -194,7 +195,9 @@ def ui_main(open_with_file=None):
194
195
  app = ApplicationFrame(root)
195
196
  app.grid(column=0, row=0, sticky=tk.NSEW)
196
197
  root.update()
197
- check_for_new_version()
198
+ # Turn off new version check because we will be switching to pypi based version checking
199
+ # (much faster - no 2 second pause on startup)
200
+ # check_for_new_version()
198
201
 
199
202
  if open_with_file and len(open_with_file) > 0:
200
203
  eventbus.emit(AppEvents.LOAD_IMAGE_REQUEST, {"filename": open_with_file})
@@ -204,7 +207,12 @@ def ui_main(open_with_file=None):
204
207
  root.mainloop()
205
208
 
206
209
 
207
- def parse_args():
210
+ def parse_args(argv):
211
+ """Parse command line arguments.
212
+
213
+ Args:
214
+ argv: list of arguments to parse. If None, uses sys.argv[1:]
215
+ """
208
216
  available_bge_versions = collect_available_versions(bge_ai_models_dir, bge_bucket_name)
209
217
  available_denoise_versions = collect_available_versions(denoise_ai_models_dir, denoise_bucket_name)
210
218
  available_deconv_obj_versions = collect_available_versions(deconvolution_object_ai_models_dir, deconvolution_object_bucket_name)
@@ -362,81 +370,125 @@ def parse_args():
362
370
  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"',
363
371
  )
364
372
 
365
- if "-h" in sys.argv or "--help" in sys.argv:
366
- if "background-extraction" in sys.argv:
373
+ if "-h" in argv or "--help" in argv:
374
+ if "background-extraction" in argv:
367
375
  bge_parser.print_help()
368
- elif "denoising" in sys.argv:
376
+ elif "denoising" in argv:
369
377
  denoise_parser.print_help()
370
- elif "deconv-obj" in sys.argv:
378
+ elif "deconv-obj" in argv:
371
379
  deconv_obj_parser.print_help()
372
- elif "deconv-stellar" in sys.argv:
380
+ elif "deconv-stellar" in argv:
373
381
  deconv_stellar_parser.print_help()
374
382
  else:
375
383
  parser.print_help()
376
384
  sys.exit(0)
377
385
 
378
- args, extras = parser.parse_known_args()
386
+ args, extras = parser.parse_known_args(argv)
379
387
 
380
388
  if args.command == "background-extraction":
381
- args = bge_parser.parse_args()
389
+ args = bge_parser.parse_args(argv)
382
390
  elif args.command == "deconv-obj":
383
- args = deconv_obj_parser.parse_args()
391
+ args = deconv_obj_parser.parse_args(argv)
384
392
  elif args.command == "deconv-stellar":
385
- args = deconv_stellar_parser.parse_args()
393
+ args = deconv_stellar_parser.parse_args(argv)
386
394
  elif args.command == "denoising":
387
- args = denoise_parser.parse_args()
395
+ args = denoise_parser.parse_args(argv)
388
396
 
389
397
  return args
390
398
 
391
- def main():
392
- """Note: this is entered directly via the entry_point definition in setup.py or called from below"""
393
399
 
394
- multiprocessing.freeze_support()
395
- faulthandler.enable(sys.__stderr__)
400
+ def main_impl(args, json_prefs: dict = {}):
401
+ """Shared main implementation for both commandline and API usage.
396
402
 
397
- try:
398
- # listing available versions might be slow, so only do it if we have command line args
399
- if len(sys.argv) > 1:
400
- args = parse_args()
401
- else:
402
- # Dummy noarg defs
403
- args = types.SimpleNamespace(command=None, filename=None)
403
+ Args:
404
+ args: parsed arguments namespace.
405
+ json_prefs: dictionary of preferences (following the documented JSON format), or None if not provided.
406
+ """
404
407
 
405
- # Note: we wait to setup logging until after parsing args, so that --help response doesn't get log framing
406
- configure_logging()
408
+ try:
409
+ # Centralize our preference file loading here, though API users will probably just pass in a dict
410
+ if args.preferences_file:
411
+ preferences_file = os.path.abspath(args.preferences_file)
412
+ if os.path.isfile(preferences_file):
413
+ with open(preferences_file, "r") as f:
414
+ json_prefs = json.load(f)
407
415
 
408
416
  if args.command == "background-extraction":
409
417
  from graxpert.cmdline_tools import BGECmdlineTool
410
418
 
411
419
  logging.info(f"Starting GraXpert CLI, Background-Extraction, version: {graxpert_version} release: {graxpert_release}")
412
420
  clt = BGECmdlineTool(args)
413
- clt.execute()
421
+ clt.execute(json_prefs)
414
422
  elif args.command == "denoising":
415
423
  from graxpert.cmdline_tools import DenoiseCmdlineTool
416
424
 
417
425
  logging.info(f"Starting GraXpert CLI, Denoising, version: {graxpert_version} release: {graxpert_release}")
418
426
  clt = DenoiseCmdlineTool(args)
419
- clt.execute()
427
+ clt.execute(json_prefs)
420
428
  elif args.command == "deconv-obj":
421
429
  from graxpert.cmdline_tools import DeconvObjCmdlineTool
422
430
 
423
431
  logging.info(f"Starting GraXpert CLI, Deconvolution Obj, version: {graxpert_version} release: {graxpert_release}")
424
432
  clt = DeconvObjCmdlineTool(args)
425
- clt.execute()
433
+ clt.execute(json_prefs)
426
434
  elif args.command == "deconv-stellar":
427
435
  from graxpert.cmdline_tools import DeconvStellarCmdlineTool
428
436
 
429
437
  logging.info(f"Starting GraXpert CLI, Deconvolution Stellar, version: {graxpert_version} release: {graxpert_release}")
430
438
  clt = DeconvStellarCmdlineTool(args)
431
- clt.execute()
439
+ clt.execute(json_prefs)
432
440
  else:
433
441
  logging.info(f"Starting GraXpert UI, version: {graxpert_version} release: {graxpert_release}")
434
442
  ui_main(args.filename)
435
443
  finally:
436
444
  temp_cleanup()
437
- logging.shutdown()
438
445
 
439
446
 
447
+ def api_run(argv, json_prefs: dict = {}):
448
+ """Allows running GraXpert programmatically via an API call, passing commandline args as a list of strings
449
+
450
+ Args:
451
+ argv: list of arguments to parse.
452
+ json_prefs: dictionary of preferences (following the documented JSON format), or None if not provided.
453
+ """
454
+
455
+ # listing available versions might be slow, so only do it if we have command line args
456
+ if len(argv) > 0:
457
+ args = parse_args(argv)
458
+ else:
459
+ # Dummy noarg defs
460
+ args = types.SimpleNamespace(command=None, filename=None)
461
+
462
+ main_impl(args, json_prefs)
463
+
464
+ def main():
465
+ """Note: this is entered directly via the entry_point definition in setup.py or called from below
466
+
467
+ Args:
468
+ argv: Optional list of arguments to parse. If None, uses sys.argv[1:]
469
+ """
470
+
471
+ multiprocessing.freeze_support()
472
+ faulthandler.enable(sys.stderr)
473
+ try:
474
+ # listing available versions might be slow, so only do it if we have command line args
475
+ if len(sys.argv) > 0:
476
+ args = parse_args(sys.argv[1:])
477
+ else:
478
+ # Dummy noarg defs
479
+ args = types.SimpleNamespace(command=None, filename=None)
480
+
481
+ # Note: we wait to setup logging until after parsing args, so that --help response doesn't get log framing
482
+ configure_logging()
483
+
484
+ main_impl(args)
485
+
486
+ logging.shutdown()
487
+ except Exception as e:
488
+ logging.exception(e)
489
+ logging.shutdown()
490
+ sys.exit(1)
491
+
440
492
  if __name__ == "__main__":
441
493
  main()
442
494
 
@@ -0,0 +1,2 @@
1
+ release = "fix smoothing typo"
2
+ version = "3.2.0a1"
@@ -0,0 +1,2 @@
1
+ release = "fix smoothing typo"
2
+ version = "SNAPSHOT"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graxpert
3
- Version: 3.2.0a0.dev7
3
+ Version: 3.2.0a1
4
4
  Summary: GraXpert is an astronomical image processing program for extracting and removing gradients in the background of your astrophotos
5
5
  Home-page: https://graxpert.com
6
6
  Author: GraXpert Development Team
@@ -15,6 +15,7 @@ Classifier: Topic :: Scientific/Engineering :: Image Processing
15
15
  Classifier: Topic :: Multimedia :: Graphics :: Editors
16
16
  Classifier: Programming Language :: Python :: 3.11
17
17
  Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
18
19
  Classifier: Operating System :: Microsoft :: Windows
19
20
  Classifier: Operating System :: MacOS
20
21
  Classifier: Operating System :: POSIX :: Linux
@@ -28,17 +29,17 @@ Description-Content-Type: text/markdown
28
29
  Requires-Dist: ml-dtypes>=0.5.3
29
30
  Requires-Dist: pillow>=11.3.0
30
31
  Requires-Dist: appdirs
31
- Requires-Dist: astropy>=6.1.7
32
+ Requires-Dist: astropy>=6.0.1
32
33
  Requires-Dist: astropy-iers-data>=0.2025.9.1.0.42.11
33
34
  Requires-Dist: customtkinter>=5.2.2
34
35
  Requires-Dist: imageio>=2.37.0
35
36
  Requires-Dist: minio>=7.2.16
36
- Requires-Dist: numpy==2.2.6
37
+ Requires-Dist: numpy<=2.2.6,>=2.0.2
37
38
  Requires-Dist: opencv-python-headless==4.11.0.86
38
39
  Requires-Dist: PyKrige>=1.7.2
39
40
  Requires-Dist: requests>=2.32.5
40
- Requires-Dist: scikit-image>=0.25.2
41
- Requires-Dist: scipy>=1.15.3
41
+ Requires-Dist: scikit-image>=0.24.0
42
+ Requires-Dist: scipy>=1.13.1
42
43
  Requires-Dist: xisf>=0.9.5
43
44
  Provides-Extra: cuda
44
45
  Requires-Dist: onnxruntime-gpu[cuda,cudnn]==1.22.0; extra == "cuda"
@@ -1,17 +1,17 @@
1
1
  ml-dtypes>=0.5.3
2
2
  pillow>=11.3.0
3
3
  appdirs
4
- astropy>=6.1.7
4
+ astropy>=6.0.1
5
5
  astropy-iers-data>=0.2025.9.1.0.42.11
6
6
  customtkinter>=5.2.2
7
7
  imageio>=2.37.0
8
8
  minio>=7.2.16
9
- numpy==2.2.6
9
+ numpy<=2.2.6,>=2.0.2
10
10
  opencv-python-headless==4.11.0.86
11
11
  PyKrige>=1.7.2
12
12
  requests>=2.32.5
13
- scikit-image>=0.25.2
14
- scipy>=1.15.3
13
+ scikit-image>=0.24.0
14
+ scipy>=1.13.1
15
15
  xisf>=0.9.5
16
16
 
17
17
  [cpuonly]
@@ -11,9 +11,9 @@ archs = ["auto64"]
11
11
  requires = [
12
12
  "setuptools>=80.9.0",
13
13
  "wheel>=0.45.1",
14
- "astropy>=7.1.0",
14
+ "astropy>=6.0.1",
15
15
  "cx_Freeze>=8.4.0",
16
16
  "imageio>=2.37.0",
17
- "scikit-image>=0.25.2"
17
+ "scikit-image>=0.24.0"
18
18
  ]
19
19
  build-backend = "setuptools.build_meta"
@@ -1,15 +1,15 @@
1
1
  ml-dtypes>=0.5.3
2
2
  pillow>=11.3.0
3
3
  appdirs
4
- astropy>=6.1.7
4
+ astropy>=6.0.1
5
5
  astropy-iers-data>=0.2025.9.1.0.42.11
6
6
  customtkinter>=5.2.2
7
7
  imageio>=2.37.0
8
8
  minio>=7.2.16
9
- numpy==2.2.6
9
+ numpy>=2.0.2,<=2.2.6
10
10
  opencv-python-headless==4.11.0.86
11
11
  PyKrige>=1.7.2
12
12
  requests>=2.32.5
13
- scikit-image>=0.25.2
14
- scipy>=1.15.3
13
+ scikit-image>=0.24.0
14
+ scipy>=1.13.1
15
15
  xisf>=0.9.5
@@ -109,6 +109,7 @@ setup_options = {
109
109
  # Supported Python versions: List all versions you test against.
110
110
  "Programming Language :: Python :: 3.11",
111
111
  "Programming Language :: Python :: 3.12",
112
+ "Programming Language :: Python :: 3.13",
112
113
 
113
114
  # Operating System: Specify which OSs are supported.
114
115
  "Operating System :: Microsoft :: Windows",
@@ -208,15 +209,17 @@ if cx_freeze_commands.intersection(sys.argv):
208
209
 
209
210
  # for exe builds we are careful to include the correct onnxruntime
210
211
  if sys.platform == "win32":
211
- build_options["excludes"] += ["onnxruntime", "onnxruntime-rocm", "onnxruntime-gpu"]
212
- # FIXME temporarily disable until we see if it is needed on windows
212
+ pass
213
+ # on windows we default to whatever onnx lib was installed at build time
214
+ # build_options["excludes"] += ["onnxruntime", "onnxruntime-rocm", "onnxruntime-gpu"]
213
215
  # build_options["includes"] += ["onnxruntime-directml"]
214
216
  else:
215
217
  build_options["excludes"] += ["onnxruntime-gpu", "onnxruntime-rocm", "onnxruntime-directml"]
216
218
  build_options["includes"] += ["onnxruntime"]
217
219
 
218
220
  # console allows passing in command line
219
- base = "Win32GUI" if sys.platform == "win32" else "console"
221
+ base = "gui" if sys.platform == "win32" else "console"
222
+ # base = "console" # if debugging on windows (or trying to use the exe in a script, then this is probably better)
220
223
 
221
224
  executables = [cx_Freeze.Executable("./graxpert/main.py", base=base,
222
225
  icon="./img/Icon", # leave off extension so it will be autocorrected for any OS
@@ -1 +0,0 @@
1
-
@@ -1,2 +0,0 @@
1
- release = "the github CI DMG builds are not yet reliable, don't require them before pypi publish"
2
- version = "3.2.0.a0.dev7"
@@ -1,2 +0,0 @@
1
- release = "the github CI DMG builds are not yet reliable, don't require them before pypi publish"
2
- version = "SNAPSHOT"
File without changes
File without changes
File without changes
File without changes
File without changes