Finale I
This commit is contained in:
Binary file not shown.
+125
-7
@@ -8,6 +8,8 @@ Abhängigkeiten:
|
||||
"""
|
||||
|
||||
import asyncio
|
||||
import base64
|
||||
import json
|
||||
import tkinter as tk
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import threading
|
||||
@@ -180,7 +182,7 @@ class AstrometryGUI:
|
||||
|
||||
def __init__(self, root: tk.Tk):
|
||||
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.minsize(800, 620)
|
||||
|
||||
@@ -278,12 +280,15 @@ class AstrometryGUI:
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
def _build_ui(self):
|
||||
self._build_menu()
|
||||
|
||||
top = ttk.Frame(self.root, padding=4)
|
||||
top.pack(fill=tk.X)
|
||||
self._build_conn_bar(top)
|
||||
|
||||
self.nb = ttk.Notebook(self.root)
|
||||
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 [
|
||||
("Input / Output", self._tab_io),
|
||||
@@ -336,6 +341,20 @@ class AstrometryGUI:
|
||||
|
||||
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
|
||||
# -----------------------------------------------------------------------
|
||||
@@ -605,7 +624,7 @@ class AstrometryGUI:
|
||||
("Weitermachen (--continue)", self.v_continue,
|
||||
"Überspringt bereits gelöste Bilder (prüft ob .solved-Datei existiert).\n"
|
||||
"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"
|
||||
"Muss auf dem Server installiert sein. Oft besser bei schwierigen Bildern."),
|
||||
("Als FITS-Bild behandeln (--fits-image)", self.v_fits_image,
|
||||
@@ -799,7 +818,7 @@ class AstrometryGUI:
|
||||
flag("--resort", self.v_resort)
|
||||
flag("--continue", self.v_continue)
|
||||
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("--no-verify", self.v_no_verify)
|
||||
flag("--just-augment", self.v_just_augment)
|
||||
@@ -967,11 +986,11 @@ class AstrometryGUI:
|
||||
except Exception as exc:
|
||||
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):
|
||||
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))
|
||||
|
||||
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.close()
|
||||
self.root.after(0, lambda: self._set_results_items(entries))
|
||||
self.root.after(0, self._clear_wait_cursor)
|
||||
except Exception as 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, self._clear_wait_cursor)
|
||||
|
||||
def _set_results_info(self, text: str):
|
||||
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:
|
||||
try:
|
||||
size = int(size_text)
|
||||
@@ -1065,8 +1091,11 @@ class AstrometryGUI:
|
||||
messagebox.showwarning("Datei fehlt", "Die ausgewählte Datei konnte nicht gefunden werden.")
|
||||
return
|
||||
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(
|
||||
title="Zielordner für Download auswählen",
|
||||
initialdir=initial_dir,
|
||||
)
|
||||
if not local_dir:
|
||||
return
|
||||
@@ -1102,7 +1131,6 @@ class AstrometryGUI:
|
||||
rc = await ssh.exec_stream(cmd, self._log)
|
||||
await ssh.close()
|
||||
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)
|
||||
except Exception as exc:
|
||||
self._log(f"[FEHLER] {exc}\n", "error")
|
||||
@@ -1267,6 +1295,96 @@ class AstrometryGUI:
|
||||
self.root.clipboard_clear()
|
||||
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)
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@@ -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": ""
|
||||
}
|
||||
@@ -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": ""
|
||||
}
|
||||
Reference in New Issue
Block a user