This commit is contained in:
2026-06-28 20:12:56 +02:00
parent 58ab0d8925
commit a8b6fa2fa6
4 changed files with 245 additions and 7 deletions
Binary file not shown.
+125 -7
View File
@@ -8,6 +8,8 @@ Abhängigkeiten:
""" """
import asyncio import asyncio
import base64
import json
import tkinter as tk import tkinter as tk
from tkinter import ttk, filedialog, messagebox, scrolledtext from tkinter import ttk, filedialog, messagebox, scrolledtext
import threading import threading
@@ -180,7 +182,7 @@ class AstrometryGUI:
def __init__(self, root: tk.Tk): def __init__(self, root: tk.Tk):
self.root = root self.root = root
self.root.title("Astrometry.net — solve-field GUI") self.root.title("Astrometry.net — solve-field GUI vom Astronomiemuseum")
self.root.geometry("1000x840") self.root.geometry("1000x840")
self.root.minsize(800, 620) self.root.minsize(800, 620)
@@ -278,12 +280,15 @@ class AstrometryGUI:
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
def _build_ui(self): def _build_ui(self):
self._build_menu()
top = ttk.Frame(self.root, padding=4) top = ttk.Frame(self.root, padding=4)
top.pack(fill=tk.X) top.pack(fill=tk.X)
self._build_conn_bar(top) self._build_conn_bar(top)
self.nb = ttk.Notebook(self.root) self.nb = ttk.Notebook(self.root)
self.nb.pack(fill=tk.BOTH, expand=True, padx=6, pady=(0, 4)) self.nb.pack(fill=tk.BOTH, expand=True, padx=6, pady=(0, 4))
self.nb.bind("<<NotebookTabChanged>>", self._on_tab_changed)
for title, builder in [ for title, builder in [
("Input / Output", self._tab_io), ("Input / Output", self._tab_io),
@@ -336,6 +341,20 @@ class AstrometryGUI:
self._current_task: asyncio.Future | None = None self._current_task: asyncio.Future | None = None
def _build_menu(self):
menubar = tk.Menu(self.root)
profile_menu = tk.Menu(menubar, tearoff=False)
profile_menu.add_command(label="Profil speichern…", command=self._save_profile)
profile_menu.add_command(label="Profil laden…", command=self._load_profile)
menubar.add_cascade(label="Profil", menu=profile_menu)
self.root.config(menu=menubar)
def _on_tab_changed(self, _event=None):
if self.nb.select() == str(self.results_tab):
self._refresh_results_tab()
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Verbindungs-Leiste # Verbindungs-Leiste
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -605,7 +624,7 @@ class AstrometryGUI:
("Weitermachen (--continue)", self.v_continue, ("Weitermachen (--continue)", self.v_continue,
"Überspringt bereits gelöste Bilder (prüft ob .solved-Datei existiert).\n" "Überspringt bereits gelöste Bilder (prüft ob .solved-Datei existiert).\n"
"Ideal für unterbrochene Batch-Verarbeitungen."), "Ideal für unterbrochene Batch-Verarbeitungen."),
("SExtractor nutzen (--use-sextractor)", self.v_sextractor, ("SExtractor nutzen (--use-source-extractor)", self.v_sextractor,
"Verwendet SExtractor statt des eingebauten Quellenerkennungs-Algorithmus.\n" "Verwendet SExtractor statt des eingebauten Quellenerkennungs-Algorithmus.\n"
"Muss auf dem Server installiert sein. Oft besser bei schwierigen Bildern."), "Muss auf dem Server installiert sein. Oft besser bei schwierigen Bildern."),
("Als FITS-Bild behandeln (--fits-image)", self.v_fits_image, ("Als FITS-Bild behandeln (--fits-image)", self.v_fits_image,
@@ -799,7 +818,7 @@ class AstrometryGUI:
flag("--resort", self.v_resort) flag("--resort", self.v_resort)
flag("--continue", self.v_continue) flag("--continue", self.v_continue)
flag("--no-delete-temp", self.v_no_del_temp) flag("--no-delete-temp", self.v_no_del_temp)
flag("--use-sextractor", self.v_sextractor) flag("--use-source-extractor", self.v_sextractor)
flag("--fits-image", self.v_fits_image) flag("--fits-image", self.v_fits_image)
flag("--no-verify", self.v_no_verify) flag("--no-verify", self.v_no_verify)
flag("--just-augment", self.v_just_augment) flag("--just-augment", self.v_just_augment)
@@ -967,11 +986,11 @@ class AstrometryGUI:
except Exception as exc: except Exception as exc:
self._log(f"[FEHLER] Remote-Inhalt l\u00f6schen: {exc}\n", "error") self._log(f"[FEHLER] Remote-Inhalt l\u00f6schen: {exc}\n", "error")
def _show_results_tab(self):
self.nb.select(self.results_tab)
def _refresh_results_tab(self): def _refresh_results_tab(self):
remote_dir = self.v_remote_dir.get().strip() remote_dir = self.v_remote_dir.get().strip()
self.root.config(cursor="watch")
self.nb.config(cursor="watch")
self.results_tree.config(cursor="watch")
self._submit(self._async_refresh_results_tab(remote_dir)) self._submit(self._async_refresh_results_tab(remote_dir))
async def _async_refresh_results_tab(self, remote_dir: str, returncode: int | None = None): async def _async_refresh_results_tab(self, remote_dir: str, returncode: int | None = None):
@@ -1015,9 +1034,11 @@ class AstrometryGUI:
await ssh.exec_stream(cmd, _capture) await ssh.exec_stream(cmd, _capture)
await ssh.close() await ssh.close()
self.root.after(0, lambda: self._set_results_items(entries)) self.root.after(0, lambda: self._set_results_items(entries))
self.root.after(0, self._clear_wait_cursor)
except Exception as exc: except Exception as exc:
self.root.after(0, lambda: self._set_results_info(f"[FEHLER] {exc}")) self.root.after(0, lambda: self._set_results_info(f"[FEHLER] {exc}"))
self.root.after(0, lambda: self._set_results_items([])) self.root.after(0, lambda: self._set_results_items([]))
self.root.after(0, self._clear_wait_cursor)
def _set_results_info(self, text: str): def _set_results_info(self, text: str):
self.results_info.config(text=text, foreground="gray") self.results_info.config(text=text, foreground="gray")
@@ -1040,6 +1061,11 @@ class AstrometryGUI:
), ),
) )
def _clear_wait_cursor(self):
self.root.config(cursor="")
self.nb.config(cursor="")
self.results_tree.config(cursor="")
def _format_size(self, size_text: str) -> str: def _format_size(self, size_text: str) -> str:
try: try:
size = int(size_text) size = int(size_text)
@@ -1065,8 +1091,11 @@ class AstrometryGUI:
messagebox.showwarning("Datei fehlt", "Die ausgewählte Datei konnte nicht gefunden werden.") messagebox.showwarning("Datei fehlt", "Die ausgewählte Datei konnte nicht gefunden werden.")
return return
suggested = os.path.basename(entry["remote"]) suggested = os.path.basename(entry["remote"])
source_dir = os.path.dirname(self.v_local_file.get().strip()) if self.v_local_file.get().strip() else ""
initial_dir = source_dir if source_dir and os.path.isdir(source_dir) else os.getcwd()
local_dir = filedialog.askdirectory( local_dir = filedialog.askdirectory(
title="Zielordner für Download auswählen", title="Zielordner für Download auswählen",
initialdir=initial_dir,
) )
if not local_dir: if not local_dir:
return return
@@ -1102,7 +1131,6 @@ class AstrometryGUI:
rc = await ssh.exec_stream(cmd, self._log) rc = await ssh.exec_stream(cmd, self._log)
await ssh.close() await ssh.close()
self._current_task = None self._current_task = None
self.root.after(0, self._show_results_tab)
await self._async_refresh_results_tab(self.v_remote_dir.get().strip(), rc) await self._async_refresh_results_tab(self.v_remote_dir.get().strip(), rc)
except Exception as exc: except Exception as exc:
self._log(f"[FEHLER] {exc}\n", "error") self._log(f"[FEHLER] {exc}\n", "error")
@@ -1267,6 +1295,96 @@ class AstrometryGUI:
self.root.clipboard_clear() self.root.clipboard_clear()
self.root.clipboard_append(self.cmd_text.get("1.0", tk.END).strip()) self.root.clipboard_append(self.cmd_text.get("1.0", tk.END).strip())
def _encrypt_text(self, text: str) -> str:
if not text:
return ""
key = b"AstrometryGuiInternalKey-2026"
plain = text.encode("utf-8")
encrypted = bytes(b ^ key[i % len(key)] for i, b in enumerate(plain))
return base64.b64encode(encrypted).decode("ascii")
def _decrypt_text(self, token: str) -> str:
if not token:
return ""
key = b"AstrometryGuiInternalKey-2026"
encrypted = base64.b64decode(token.encode("ascii"))
plain = bytes(b ^ key[i % len(key)] for i, b in enumerate(encrypted))
return plain.decode("utf-8")
def _collect_profile_data(self) -> dict:
vars_data = {}
for name, value in self.__dict__.items():
if name.startswith("v_") and hasattr(value, "get"):
vars_data[name] = value.get()
if "v_pass" in vars_data:
vars_data["v_pass"] = ""
return {
"format": 2,
"variables": vars_data,
"secrets": {
"v_pass_enc": self._encrypt_text(self.v_pass.get()),
},
"idx_text": self.idx_text.get("1.0", tk.END).strip(),
}
def _apply_profile_data(self, data: dict):
vars_data = data.get("variables", {})
for name, value in vars_data.items():
var = getattr(self, name, None)
if var is not None and hasattr(var, "set"):
var.set(value)
secrets = data.get("secrets", {})
encrypted_pass = secrets.get("v_pass_enc") or secrets.get("v_pass_dpapi")
if encrypted_pass:
try:
self.v_pass.set(self._decrypt_text(encrypted_pass))
except Exception:
pass
self.idx_text.delete("1.0", tk.END)
idx_text = data.get("idx_text", "")
if idx_text:
self.idx_text.insert("1.0", idx_text)
self._refresh_cmd()
def _save_profile(self):
profile_dir = os.path.join(os.getcwd(), "profiles")
os.makedirs(profile_dir, exist_ok=True)
path = filedialog.asksaveasfilename(
title="Profil speichern",
initialdir=profile_dir,
defaultextension=".json",
filetypes=[("JSON-Dateien", "*.json"), ("Alle Dateien", "*.*")],
)
if not path:
return
data = self._collect_profile_data()
try:
with open(path, "w", encoding="utf-8") as f:
json.dump(data, f, ensure_ascii=False, indent=2)
self._log(f"[OK] Profil gespeichert: {path}\n", "ok")
except Exception as exc:
messagebox.showerror("Profil speichern", f"Speichern fehlgeschlagen:\n{exc}")
def _load_profile(self):
profile_dir = os.path.join(os.getcwd(), "profiles")
path = filedialog.askopenfilename(
title="Profil laden",
initialdir=profile_dir if os.path.isdir(profile_dir) else os.getcwd(),
filetypes=[("JSON-Dateien", "*.json"), ("Alle Dateien", "*.*")],
)
if not path:
return
try:
with open(path, "r", encoding="utf-8") as f:
data = json.load(f)
self._apply_profile_data(data)
self._log(f"[OK] Profil geladen: {path}\n", "ok")
except Exception as exc:
messagebox.showerror("Profil laden", f"Laden fehlgeschlagen:\n{exc}")
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Log (thread-sicher via Queue) # Log (thread-sicher via Queue)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
+60
View File
@@ -0,0 +1,60 @@
{
"format": 2,
"variables": {
"v_host": "10.42.44.25",
"v_port": 22,
"v_user": "eskimue",
"v_pass": "",
"v_local_file": "",
"v_remote_dir": "/tmp/astrometry",
"v_input_file": "",
"v_out_prefix": "",
"v_out_dir": "/tmp/astrometry",
"v_new_fits": "",
"v_wcs": "",
"v_corr": "",
"v_rdls": "",
"v_axy": "",
"v_solved": "",
"v_match": "",
"v_scale_units": "degwidth",
"v_scale_low": "20",
"v_scale_high": "30",
"v_scale_est": "",
"v_scale_err": "",
"v_ra": "",
"v_dec": "",
"v_radius": "1.0",
"v_depth": "",
"v_cpulimit": "300",
"v_plot_scale": "",
"v_plot_scale_auto": true,
"v_plot_target_long_edge": "900",
"v_downsample": "6",
"v_tweak_order": "2",
"v_odds_solve": "",
"v_odds_reject": "",
"v_odds_tune": "",
"v_objs": "",
"v_pixel_error": "",
"v_parity": "both",
"v_overwrite": true,
"v_no_plots": false,
"v_no_tweak": false,
"v_resort": false,
"v_verbose": false,
"v_timestamp": true,
"v_no_del_temp": false,
"v_continue": false,
"v_sextractor": false,
"v_fits_image": false,
"v_no_verify": false,
"v_just_augment": false,
"v_invert": true,
"v_extra": ""
},
"secrets": {
"v_pass_enc": "EQERAQYJAA=="
},
"idx_text": ""
}
+60
View File
@@ -0,0 +1,60 @@
{
"format": 2,
"variables": {
"v_host": "10.42.44.25",
"v_port": 22,
"v_user": "eskimue",
"v_pass": "",
"v_local_file": "C:/Users/ich/Cloud__Museum/Museumsentwicklung/Shop/Faksimile/2026/Heinlein Dieter/IMG_410328-1956_08_30-20000+2000-Te3-26-50.png",
"v_remote_dir": "/tmp/astrometry",
"v_input_file": "/tmp/astrometry/IMG_410328-1956_08_30-20000+2000-Te3-26-50.png",
"v_out_prefix": "",
"v_out_dir": "/tmp/astrometry",
"v_new_fits": "/tmp/astrometry/IMG_410328-1956_08_30-20000+2000-Te3-26-50.fits",
"v_wcs": "",
"v_corr": "",
"v_rdls": "",
"v_axy": "",
"v_solved": "",
"v_match": "",
"v_scale_units": "degwidth",
"v_scale_low": "20",
"v_scale_high": "30",
"v_scale_est": "",
"v_scale_err": "",
"v_ra": "",
"v_dec": "",
"v_radius": "1.0",
"v_depth": "",
"v_cpulimit": "300",
"v_plot_scale": "0.15",
"v_plot_scale_auto": true,
"v_plot_target_long_edge": "900",
"v_downsample": "6",
"v_tweak_order": "2",
"v_odds_solve": "",
"v_odds_reject": "",
"v_odds_tune": "",
"v_objs": "",
"v_pixel_error": "",
"v_parity": "both",
"v_overwrite": true,
"v_no_plots": false,
"v_no_tweak": false,
"v_resort": false,
"v_verbose": false,
"v_timestamp": true,
"v_no_del_temp": false,
"v_continue": false,
"v_sextractor": false,
"v_fits_image": false,
"v_no_verify": false,
"v_just_augment": false,
"v_invert": true,
"v_extra": ""
},
"secrets": {
"v_pass_enc": "EQERAQYJAA=="
},
"idx_text": ""
}