Kleinigkeiten
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"host": "10.42.42.102",
|
||||
"port": 22,
|
||||
"user": "tm",
|
||||
"password": "heute1234"
|
||||
}
|
||||
@@ -0,0 +1,248 @@
|
||||
# Solve-Field-GUI
|
||||
|
||||
Grafische Windows-Oberfläche für `solve-field` aus Astrometry.net mit Ausführung auf einem Linux-Server per SSH.
|
||||
|
||||
Die Software richtet sich an Anwender, die Astrometrie komfortabel über eine GUI steuern möchten, ohne die `solve-field`-Befehle jedes Mal von Hand zusammenzubauen.
|
||||
|
||||
## Was die Software macht
|
||||
|
||||
Die Anwendung läuft lokal unter Windows und verbindet sich per SSH mit einem entfernten Linux-Server.
|
||||
|
||||
Dort kann sie:
|
||||
|
||||
- Bilddateien auf den Server hochladen
|
||||
- `solve-field` mit vielen Parametern starten
|
||||
- die Ausgabe live im Log anzeigen
|
||||
- Ergebnisdateien im Remote-Verzeichnis anzeigen
|
||||
- einzelne oder mehrere Ergebnisdateien wieder lokal herunterladen
|
||||
- Serverfunktionen wie SExtractor-Prüfung und Diagnose ausführen
|
||||
|
||||
## Hauptfunktionen
|
||||
|
||||
- SSH-Verbindung zu einem Linux-Server mit Benutzername, Passwort und Port
|
||||
- Upload lokaler Bilddateien per SFTP
|
||||
- grafische Konfiguration fast aller wichtigen `solve-field`-Parameter
|
||||
- automatische Befehlsvorschau des erzeugten `solve-field`-Aufrufs
|
||||
- Unterstützung für Maßstab, RA/Dek, Suchradius, Downsampling, CPU-Limit, Tweak-Order und viele weitere Optionen
|
||||
- Unterstützung für Source Extractor per `--use-source-extractor`
|
||||
- automatische Berechnung von `--plot-scale` aus der lokalen Bildgröße
|
||||
- serverseitige Diagnose für `solve-field`, `astrometry.cfg`, Indexdateien, SExtractor und Speicherplatz
|
||||
- Remote-Ergebnis-Tab mit Dateiliste
|
||||
- Mehrfachdownload ausgewählter Ergebnisdateien
|
||||
- Profilspeicherung und Profilladen für komplette GUI-Einstellungen
|
||||
|
||||
## Unterstützte Dateitypen
|
||||
|
||||
Als Eingabedatei lokal:
|
||||
|
||||
- FITS
|
||||
- FIT
|
||||
- FTS
|
||||
- JPG / JPEG
|
||||
- PNG
|
||||
- TIFF / TIF
|
||||
|
||||
Für die automatische Plot-Skalierung kann die Anwendung Bildgrößen direkt aus mehreren Formaten lesen, darunter FITS, PNG, JPG, GIF und BMP.
|
||||
|
||||
## Voraussetzungen
|
||||
|
||||
## Lokal unter Windows
|
||||
|
||||
- Python 3
|
||||
- `asyncssh`
|
||||
- Netzwerkzugriff auf den Linux-Server per SSH
|
||||
|
||||
Installation von `asyncssh`:
|
||||
|
||||
```bash
|
||||
pip install asyncssh
|
||||
```
|
||||
|
||||
## Auf dem Linux-Server
|
||||
|
||||
- Astrometry.net mit `solve-field`
|
||||
- passende Indexdateien
|
||||
- SSH-Zugang
|
||||
|
||||
Optional:
|
||||
|
||||
- `source-extractor`, wenn SExtractor zur Quellenerkennung genutzt werden soll
|
||||
|
||||
## Starten
|
||||
|
||||
Direkt mit Python:
|
||||
|
||||
```bash
|
||||
python astrometry_gui.py
|
||||
```
|
||||
|
||||
Oder unter Windows über:
|
||||
|
||||
```bash
|
||||
astrometry_gui.bat
|
||||
```
|
||||
|
||||
## Bedienung
|
||||
|
||||
## 1. SSH eintragen
|
||||
|
||||
Oben in der GUI:
|
||||
|
||||
- Host
|
||||
- Port
|
||||
- Benutzer
|
||||
- Passwort
|
||||
|
||||
Mit `Verbinden & testen` kann geprüft werden, ob der Server erreichbar ist und `solve-field` vorhanden ist.
|
||||
|
||||
## 2. Bild auswählen und hochladen
|
||||
|
||||
Im Tab `Input / Output`:
|
||||
|
||||
- lokale Datei auswählen
|
||||
- Remote-Verzeichnis festlegen
|
||||
- Datei per `Hochladen` auf den Server übertragen
|
||||
|
||||
Nach dem Upload wird der Remote-Dateipfad automatisch als Eingabedatei gesetzt.
|
||||
|
||||
## 3. Solver-Parameter einstellen
|
||||
|
||||
Je nach Bedarf können unter anderem gesetzt werden:
|
||||
|
||||
- Maßstabsbereich
|
||||
- RA / Dek / Radius
|
||||
- CPU-Limit
|
||||
- Downsampling
|
||||
- Plot-Skalierung
|
||||
- Tweak-Order
|
||||
- Verifikation
|
||||
- Invertierung
|
||||
- SExtractor-Nutzung
|
||||
|
||||
## 4. Befehl prüfen und starten
|
||||
|
||||
Im Tab `Befehlsvorschau` sieht man den tatsächlich erzeugten `solve-field`-Befehl.
|
||||
|
||||
Mit `solve-field starten` wird der Befehl auf dem Server ausgeführt.
|
||||
|
||||
## Tabs im Überblick
|
||||
|
||||
## Input / Output
|
||||
|
||||
- lokale Bilddatei
|
||||
- Remote-Verzeichnis
|
||||
- Upload
|
||||
- Eingabedatei auf dem Server
|
||||
- Ausgabeoptionen wie `--wcs`, `--corr`, `--rdls`, `--match`, `--new-fits`
|
||||
|
||||
## Maßstab & Position
|
||||
|
||||
- `--scale-low`
|
||||
- `--scale-high`
|
||||
- `--scale`
|
||||
- `--scale-err`
|
||||
- `--ra`
|
||||
- `--dec`
|
||||
- `--radius`
|
||||
|
||||
## Solver-Parameter
|
||||
|
||||
- `--depth`
|
||||
- `--cpulimit`
|
||||
- `--plot-scale`
|
||||
- `--downsample`
|
||||
- `--tweak-order`
|
||||
- `--odds-*`
|
||||
- `--objs`
|
||||
- `--pixel-error`
|
||||
- zahlreiche Flags wie `--no-plots`, `--invert`, `--no-verify`
|
||||
|
||||
Zusätzlich gibt es einen Plot-Helfer:
|
||||
|
||||
- automatische Berechnung von `plot-scale`
|
||||
- Zielkantenlänge einstellbar
|
||||
|
||||
## Erweitert
|
||||
|
||||
- manuelle Indexdateien
|
||||
- zusätzliche freie Kommandozeilenargumente
|
||||
|
||||
## Befehlsvorschau
|
||||
|
||||
- zeigt den generierten Befehl
|
||||
- kann aktualisiert werden
|
||||
- kann in die Zwischenablage kopiert werden
|
||||
|
||||
## Server Setup
|
||||
|
||||
- SExtractor prüfen
|
||||
- SExtractor installieren
|
||||
- Server-Diagnose ausführen
|
||||
|
||||
## Remote-Ergebnisse
|
||||
|
||||
- zeigt Dateien im aktuellen Remote-Verzeichnis
|
||||
- lädt den Inhalt beim Öffnen des Tabs automatisch neu
|
||||
- erlaubt Auswahl und Download von einer oder mehreren Dateien
|
||||
|
||||
## Remote-Ergebnisse herunterladen
|
||||
|
||||
Im Tab `Remote-Ergebnisse`:
|
||||
|
||||
- eine oder mehrere Dateien markieren
|
||||
- `Herunterladen` klicken
|
||||
- Zielordner auswählen
|
||||
|
||||
Standardmäßig wird als Zielordner der Ordner der ursprünglich lokal ausgewählten Bilddatei vorgeschlagen.
|
||||
|
||||
## Remote-Verzeichnis leeren
|
||||
|
||||
Im Tab `Input / Output` gibt es die Funktion:
|
||||
|
||||
- `Remote-Inhalt löschen`
|
||||
|
||||
Dabei wird nur der Inhalt des Remote-Verzeichnisses gelöscht, nicht das Verzeichnis selbst.
|
||||
|
||||
## Profile speichern und laden
|
||||
|
||||
Über die Menühleiste:
|
||||
|
||||
- `Profil -> Profil speichern…`
|
||||
- `Profil -> Profil laden…`
|
||||
|
||||
Gespeichert werden:
|
||||
|
||||
- SSH-Daten
|
||||
- Solver-Parameter
|
||||
- Ein-/Ausgabe-Einstellungen
|
||||
- Zusatzargumente
|
||||
- Indexliste
|
||||
|
||||
Das Passwort wird nicht im Klartext gespeichert, sondern mit einer einfachen internen Verschlüsselung abgelegt.
|
||||
|
||||
## Typischer Workflow
|
||||
|
||||
1. Serververbindung eintragen
|
||||
2. Lokales Bild auswählen
|
||||
3. Datei hochladen
|
||||
4. Maßstab und Solver-Parameter setzen
|
||||
5. Optional SExtractor aktivieren
|
||||
6. `solve-field` starten
|
||||
7. Ergebnisse im Tab `Remote-Ergebnisse` prüfen
|
||||
8. Gewünschte Dateien lokal herunterladen
|
||||
|
||||
## Hinweise
|
||||
|
||||
- Die GUI baut nur den Befehl und führt ihn per SSH aus. Die eigentliche Astrometrie läuft vollständig auf dem Server.
|
||||
- Die installierte Astrometry-Version auf dem Server bestimmt, welche Optionen tatsächlich verfügbar sind.
|
||||
- Für SExtractor muss auf dem Server in der Regel `source-extractor` installiert sein.
|
||||
- Für gute Ergebnisse müssen die passenden Indexdateien serverseitig vorhanden und in `astrometry.cfg` korrekt eingebunden sein.
|
||||
|
||||
## Dateien im Projekt
|
||||
|
||||
- [astrometry_gui.py](C:/Users/ich/source/repos/Solve-Field-GUI/astrometry_gui.py)
|
||||
- [astrometry_gui.bat](C:/Users/ich/source/repos/Solve-Field-GUI/astrometry_gui.bat)
|
||||
|
||||
## Lizenz / Nutzung
|
||||
|
||||
Dieses Repository enthält eine GUI zur Bedienung von Astrometry.net. Die Lizenz und Bedingungen von Astrometry.net sowie von eventuell installierten Serverpaketen gelten zusätzlich zu diesem Projekt.
|
||||
Binary file not shown.
+96
-15
@@ -11,6 +11,7 @@ import asyncio
|
||||
import base64
|
||||
import json
|
||||
import tkinter as tk
|
||||
import tkinter.font as tkfont
|
||||
from tkinter import ttk, filedialog, messagebox, scrolledtext
|
||||
import threading
|
||||
import os
|
||||
@@ -26,6 +27,9 @@ except ImportError:
|
||||
HAS_ASYNCSSH = False
|
||||
|
||||
|
||||
SSH_CREDENTIALS_JSON = "ssh_zugangsdaten.json"
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Tooltip
|
||||
# ---------------------------------------------------------------------------
|
||||
@@ -215,6 +219,7 @@ class AstrometryGUI:
|
||||
self.v_port = tk.IntVar(value=22)
|
||||
self.v_user = tk.StringVar(value="eskimue")
|
||||
self.v_pass = tk.StringVar(value="Preside")
|
||||
self._load_startup_credentials()
|
||||
|
||||
self.v_local_file = tk.StringVar()
|
||||
self.v_local_file.trace_add("write", self._on_local_file_changed)
|
||||
@@ -275,6 +280,27 @@ class AstrometryGUI:
|
||||
self.v_extra = tk.StringVar()
|
||||
self.results_files: dict[str, dict] = {}
|
||||
|
||||
def _load_startup_credentials(self):
|
||||
path = os.path.join(os.getcwd(), SSH_CREDENTIALS_JSON)
|
||||
if not os.path.isfile(path):
|
||||
return
|
||||
try:
|
||||
self._load_credentials_from_file(path)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
def _load_credentials_from_file(self, path: str):
|
||||
with open(path, "r", encoding="utf-8") as f:
|
||||
data = json.load(f)
|
||||
if "host" in data:
|
||||
self.v_host.set(str(data["host"]))
|
||||
if "port" in data:
|
||||
self.v_port.set(int(data["port"]))
|
||||
if "user" in data:
|
||||
self.v_user.set(str(data["user"]))
|
||||
if "password" in data:
|
||||
self.v_pass.set(str(data["password"]))
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# UI aufbauen
|
||||
# -----------------------------------------------------------------------
|
||||
@@ -345,6 +371,8 @@ class AstrometryGUI:
|
||||
menubar = tk.Menu(self.root)
|
||||
|
||||
profile_menu = tk.Menu(menubar, tearoff=False)
|
||||
profile_menu.add_command(label="Verbindung laden…", command=self._menu_load_credentials)
|
||||
profile_menu.add_separator()
|
||||
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)
|
||||
@@ -399,6 +427,9 @@ class AstrometryGUI:
|
||||
"Öffnet einen Datei-Dialog um die lokale Astro-Bild-Datei auszuwählen."
|
||||
).grid(row=0, column=2, padx=4)
|
||||
|
||||
self.local_image_size_label = ttk.Label(uf, text="Größe: —", foreground="gray")
|
||||
self.local_image_size_label.grid(row=0, column=3, sticky=tk.W, padx=4, pady=3)
|
||||
|
||||
ttk.Label(uf, text="Remote-Verzeichnis:").grid(row=1, column=0, sticky=tk.W, padx=4, pady=3)
|
||||
tip(ttk.Entry(uf, textvariable=self.v_remote_dir),
|
||||
"Verzeichnis auf dem Server wohin die Datei hochgeladen wird.\n"
|
||||
@@ -772,6 +803,9 @@ class AstrometryGUI:
|
||||
tip(ttk.Button(head, text="Aktualisieren", command=self._refresh_results_tab),
|
||||
"Liest den Inhalt des aktuell eingestellten Remote-Verzeichnisses erneut vom Server ein."
|
||||
).pack(side=tk.RIGHT, padx=4)
|
||||
tip(ttk.Button(head, text="Remote-Inhalt löschen", command=self._delete_remote_dir),
|
||||
"Löscht den Inhalt des aktuell eingestellten Remote-Verzeichnisses auf dem Server."
|
||||
).pack(side=tk.RIGHT, padx=4)
|
||||
tip(ttk.Button(head, text="Herunterladen", command=self._download_selected_result),
|
||||
"Lädt die ausgewählte Remote-Datei per SFTP herunter."
|
||||
).pack(side=tk.RIGHT, padx=4)
|
||||
@@ -780,7 +814,7 @@ class AstrometryGUI:
|
||||
self.results_info.pack(anchor=tk.W, pady=(0, 4))
|
||||
|
||||
cols = ("name", "size", "remote")
|
||||
self.results_tree = ttk.Treeview(parent, columns=cols, show="headings", height=16)
|
||||
self.results_tree = ttk.Treeview(parent, columns=cols, show="headings", height=16, selectmode="extended")
|
||||
self.results_tree.heading("name", text="Datei")
|
||||
self.results_tree.heading("size", text="Größe")
|
||||
self.results_tree.heading("remote", text="Remote-Pfad")
|
||||
@@ -1060,6 +1094,20 @@ class AstrometryGUI:
|
||||
entry["remote"],
|
||||
),
|
||||
)
|
||||
self._autosize_results_columns()
|
||||
|
||||
def _autosize_results_columns(self):
|
||||
body_font = tkfont.nametofont("TkDefaultFont")
|
||||
head_font = tkfont.nametofont("TkHeadingFont")
|
||||
padding = 24
|
||||
|
||||
for col in ("name", "size", "remote"):
|
||||
header = self.results_tree.heading(col, "text") or col
|
||||
width = head_font.measure(header) + padding
|
||||
for item_id in self.results_tree.get_children():
|
||||
value = self.results_tree.set(item_id, col)
|
||||
width = max(width, body_font.measure(str(value)) + padding)
|
||||
self.results_tree.column(col, width=width)
|
||||
|
||||
def _clear_wait_cursor(self):
|
||||
self.root.config(cursor="")
|
||||
@@ -1084,13 +1132,16 @@ class AstrometryGUI:
|
||||
def _download_selected_result(self):
|
||||
selection = self.results_tree.selection()
|
||||
if not selection:
|
||||
messagebox.showwarning("Keine Datei ausgewählt", "Bitte zuerst eine Datei im Ergebnis-Tab auswählen.")
|
||||
messagebox.showwarning("Keine Datei ausgewählt", "Bitte zuerst eine oder mehrere Dateien im Ergebnis-Tab auswählen.")
|
||||
return
|
||||
entry = self.results_files.get(selection[0])
|
||||
if not entry:
|
||||
messagebox.showwarning("Datei fehlt", "Die ausgewählte Datei konnte nicht gefunden werden.")
|
||||
entries = []
|
||||
for item_id in selection:
|
||||
entry = self.results_files.get(item_id)
|
||||
if entry:
|
||||
entries.append(entry)
|
||||
if not entries:
|
||||
messagebox.showwarning("Datei fehlt", "Die ausgewählten Dateien konnten 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(
|
||||
@@ -1099,20 +1150,25 @@ class AstrometryGUI:
|
||||
)
|
||||
if not local_dir:
|
||||
return
|
||||
local_path = os.path.normpath(os.path.join(local_dir, suggested))
|
||||
self._submit(self._async_download_result(entry["remote"], local_path))
|
||||
downloads = []
|
||||
for entry in entries:
|
||||
suggested = os.path.basename(entry["remote"])
|
||||
local_path = os.path.normpath(os.path.join(local_dir, suggested))
|
||||
downloads.append((entry["remote"], local_path))
|
||||
self._submit(self._async_download_results(downloads))
|
||||
|
||||
async def _async_download_result(self, remote_path: str, local_path: str):
|
||||
self._log(f"[INFO] Lade Ergebnis herunter: {remote_path} → {local_path}\n", "info")
|
||||
async def _async_download_results(self, downloads: list[tuple[str, str]]):
|
||||
try:
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
ssh = self._make_ssh()
|
||||
await ssh.connect()
|
||||
await ssh.download(remote_path, local_path)
|
||||
for remote_path, local_path in downloads:
|
||||
self._log(f"[INFO] Lade Ergebnis herunter: {remote_path} → {local_path}\n", "info")
|
||||
os.makedirs(os.path.dirname(local_path), exist_ok=True)
|
||||
await ssh.download(remote_path, local_path)
|
||||
if not os.path.isfile(local_path):
|
||||
raise FileNotFoundError(f"Datei nach Download nicht gefunden: {local_path}")
|
||||
self._log(f"[OK] Download abgeschlossen: {local_path}\n", "ok")
|
||||
await ssh.close()
|
||||
if not os.path.isfile(local_path):
|
||||
raise FileNotFoundError(f"Datei nach Download nicht gefunden: {local_path}")
|
||||
self._log(f"[OK] Download abgeschlossen: {local_path}\n", "ok")
|
||||
except Exception as exc:
|
||||
self._log(f"[FEHLER] Download: {exc}\n", "error")
|
||||
|
||||
@@ -1159,6 +1215,15 @@ class AstrometryGUI:
|
||||
self.v_new_fits.set(auto_path)
|
||||
|
||||
def _on_local_file_changed(self, *_):
|
||||
path = self.v_local_file.get().strip()
|
||||
if not path or not os.path.isfile(path):
|
||||
self.local_image_size_label.config(text="Größe: —")
|
||||
else:
|
||||
try:
|
||||
width, height = self._read_image_size(path)
|
||||
self.local_image_size_label.config(text=f"Größe: {width} × {height}")
|
||||
except Exception:
|
||||
self.local_image_size_label.config(text="Größe: unbekannt")
|
||||
if self.v_plot_scale_auto.get():
|
||||
self._recalculate_plot_scale()
|
||||
|
||||
@@ -1285,6 +1350,8 @@ class AstrometryGUI:
|
||||
)
|
||||
if path:
|
||||
self.v_local_file.set(path)
|
||||
if messagebox.askyesno("Datei hochladen", "Soll die ausgewählte Datei jetzt direkt auf den Server hochgeladen werden?"):
|
||||
self._upload()
|
||||
|
||||
def _refresh_cmd(self):
|
||||
cmd = self._build_command()
|
||||
@@ -1385,6 +1452,20 @@ class AstrometryGUI:
|
||||
except Exception as exc:
|
||||
messagebox.showerror("Profil laden", f"Laden fehlgeschlagen:\n{exc}")
|
||||
|
||||
def _menu_load_credentials(self):
|
||||
path = filedialog.askopenfilename(
|
||||
title="Zugangsdaten laden",
|
||||
initialdir=os.getcwd(),
|
||||
filetypes=[("JSON-Dateien", "*.json"), ("Alle Dateien", "*.*")],
|
||||
)
|
||||
if not path:
|
||||
return
|
||||
try:
|
||||
self._load_credentials_from_file(path)
|
||||
self._log(f"[OK] Zugangsdaten geladen: {path}\n", "ok")
|
||||
except Exception as exc:
|
||||
messagebox.showerror("Zugangsdaten laden", f"Laden fehlgeschlagen:\n{exc}")
|
||||
|
||||
# -----------------------------------------------------------------------
|
||||
# Log (thread-sicher via Queue)
|
||||
# -----------------------------------------------------------------------
|
||||
|
||||
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"host": "10.42.44.25",
|
||||
"port": 22,
|
||||
"user": "eskimue",
|
||||
"password": "Preside"
|
||||
}
|
||||
Reference in New Issue
Block a user