Finale I
This commit is contained in:
Binary file not shown.
+125
-7
@@ -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)
|
||||||
# -----------------------------------------------------------------------
|
# -----------------------------------------------------------------------
|
||||||
|
|||||||
@@ -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