setiastrosuitepro 1.6.1__py3-none-any.whl → 1.6.1.post1__py3-none-any.whl
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.
- setiastro/saspro/__main__.py +4 -5
- setiastro/saspro/_generated/build_info.py +1 -1
- setiastro/saspro/gui/main_window.py +3 -3
- setiastro/saspro/plate_solver.py +1 -1
- setiastro/saspro/versioning.py +5 -30
- setiastro/saspro/wimi.py +7976 -0
- setiastro/saspro/wims.py +578 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/METADATA +1 -1
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/RECORD +13 -11
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/WHEEL +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/entry_points.txt +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/licenses/LICENSE +0 -0
- {setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/licenses/license.txt +0 -0
setiastro/saspro/wims.py
ADDED
|
@@ -0,0 +1,578 @@
|
|
|
1
|
+
# whatsinmysky.py
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
# --- stdlib ---
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import shutil
|
|
8
|
+
import warnings
|
|
9
|
+
import webbrowser
|
|
10
|
+
from datetime import datetime
|
|
11
|
+
from decimal import getcontext
|
|
12
|
+
from typing import Optional
|
|
13
|
+
|
|
14
|
+
# --- third-party ---
|
|
15
|
+
import numpy as np
|
|
16
|
+
import pandas as pd
|
|
17
|
+
import pytz
|
|
18
|
+
from astropy import units as u
|
|
19
|
+
from astropy.coordinates import SkyCoord, EarthLocation, AltAz, get_sun, get_body
|
|
20
|
+
from astropy.time import Time
|
|
21
|
+
|
|
22
|
+
# --- Qt / PyQt6 ---
|
|
23
|
+
from PyQt6.QtCore import Qt, QThread, pyqtSignal, QSettings
|
|
24
|
+
from PyQt6.QtGui import QIcon, QPixmap
|
|
25
|
+
from PyQt6.QtWidgets import (
|
|
26
|
+
QDialog, QLabel, QLineEdit, QComboBox, QCheckBox, QRadioButton, QButtonGroup,
|
|
27
|
+
QPushButton, QGridLayout, QTreeWidget, QTreeWidgetItem, QHeaderView, QFileDialog,
|
|
28
|
+
QScrollArea, QInputDialog, QMessageBox, QWidget, QHBoxLayout
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
# ---------------------------------------------------
|
|
32
|
+
# paths / globals
|
|
33
|
+
# ---------------------------------------------------
|
|
34
|
+
def _app_root() -> str:
|
|
35
|
+
# this file sits next to setiastrosuitepro.py and imgs/
|
|
36
|
+
return getattr(sys, "_MEIPASS", os.path.dirname(__file__))
|
|
37
|
+
|
|
38
|
+
def imgs_path(*parts) -> str:
|
|
39
|
+
return os.path.join(_app_root(), "imgs", *parts)
|
|
40
|
+
|
|
41
|
+
getcontext().prec = 24
|
|
42
|
+
warnings.filterwarnings("ignore")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
# ---------------------------------------------------
|
|
46
|
+
# Worker thread
|
|
47
|
+
# ---------------------------------------------------
|
|
48
|
+
class CalculationThread(QThread):
|
|
49
|
+
calculation_complete = pyqtSignal(pd.DataFrame, str)
|
|
50
|
+
lunar_phase_calculated = pyqtSignal(int, str) # phase_percentage, phase_image_name
|
|
51
|
+
lst_calculated = pyqtSignal(str)
|
|
52
|
+
status_update = pyqtSignal(str)
|
|
53
|
+
|
|
54
|
+
def __init__(
|
|
55
|
+
self,
|
|
56
|
+
latitude: float,
|
|
57
|
+
longitude: float,
|
|
58
|
+
date: str,
|
|
59
|
+
time: str,
|
|
60
|
+
timezone: str,
|
|
61
|
+
min_altitude: float,
|
|
62
|
+
catalog_filters: list[str],
|
|
63
|
+
object_limit: int,
|
|
64
|
+
):
|
|
65
|
+
super().__init__()
|
|
66
|
+
self.latitude = float(latitude)
|
|
67
|
+
self.longitude = float(longitude)
|
|
68
|
+
self.date = date
|
|
69
|
+
self.time = time
|
|
70
|
+
self.timezone = timezone
|
|
71
|
+
self.min_altitude = float(min_altitude)
|
|
72
|
+
self.catalog_filters = list(catalog_filters or [])
|
|
73
|
+
self.object_limit = int(object_limit)
|
|
74
|
+
|
|
75
|
+
self.catalog_file = self.get_catalog_file_path()
|
|
76
|
+
|
|
77
|
+
def get_catalog_file_path(self) -> str:
|
|
78
|
+
user_catalog_path = os.path.join(os.path.expanduser("~"), "celestial_catalog.csv")
|
|
79
|
+
if not os.path.exists(user_catalog_path):
|
|
80
|
+
bundled = os.path.join(_app_root(), "data", "catalogs", "celestial_catalog.csv")
|
|
81
|
+
if os.path.exists(bundled):
|
|
82
|
+
try: shutil.copyfile(bundled, user_catalog_path)
|
|
83
|
+
except Exception as e:
|
|
84
|
+
import logging
|
|
85
|
+
logging.debug(f"Exception suppressed: {type(e).__name__}: {e}")
|
|
86
|
+
return user_catalog_path
|
|
87
|
+
|
|
88
|
+
def run(self):
|
|
89
|
+
try:
|
|
90
|
+
# local date/time → astropy Time
|
|
91
|
+
local_tz = pytz.timezone(self.timezone)
|
|
92
|
+
naive = datetime.strptime(f"{self.date} {self.time}", "%Y-%m-%d %H:%M")
|
|
93
|
+
local_dt = local_tz.localize(naive)
|
|
94
|
+
t = Time(local_dt)
|
|
95
|
+
|
|
96
|
+
# observer + LST
|
|
97
|
+
loc = EarthLocation(lat=self.latitude * u.deg, lon=self.longitude * u.deg, height=0 * u.m)
|
|
98
|
+
lst = t.sidereal_time("apparent", self.longitude * u.deg)
|
|
99
|
+
self.lst_calculated.emit(f"Local Sidereal Time: {lst.to_string(unit=u.hour, precision=3)}")
|
|
100
|
+
|
|
101
|
+
# moon phase + icon
|
|
102
|
+
phase_pct, phase_icon = self.calculate_lunar_phase(t, loc)
|
|
103
|
+
self.lunar_phase_calculated.emit(phase_pct, phase_icon)
|
|
104
|
+
|
|
105
|
+
# load catalog
|
|
106
|
+
catalog_file = self.catalog_file
|
|
107
|
+
if not os.path.exists(catalog_file):
|
|
108
|
+
self.calculation_complete.emit(pd.DataFrame(), "Catalog file not found.")
|
|
109
|
+
return
|
|
110
|
+
df = pd.read_csv(catalog_file, encoding="ISO-8859-1")
|
|
111
|
+
|
|
112
|
+
if self.catalog_filters:
|
|
113
|
+
df = df[df["Catalog"].isin(self.catalog_filters)]
|
|
114
|
+
df.dropna(subset=["RA", "Dec"], inplace=True)
|
|
115
|
+
df.reset_index(drop=True, inplace=True)
|
|
116
|
+
|
|
117
|
+
# coordinates → AltAz
|
|
118
|
+
sky = SkyCoord(ra=df["RA"].to_numpy() * u.deg, dec=df["Dec"].to_numpy() * u.deg, frame="icrs")
|
|
119
|
+
altaz_frame = AltAz(obstime=t, location=loc)
|
|
120
|
+
altaz = sky.transform_to(altaz_frame)
|
|
121
|
+
df["Altitude"] = np.round(altaz.alt.deg, 1)
|
|
122
|
+
df["Azimuth"] = np.round(altaz.az.deg, 1)
|
|
123
|
+
|
|
124
|
+
# separation from Moon
|
|
125
|
+
moon_altaz = get_body("moon", t, loc).transform_to(altaz_frame)
|
|
126
|
+
df["Degrees from Moon"] = np.round(altaz.separation(moon_altaz).deg, 2)
|
|
127
|
+
|
|
128
|
+
# altitude gate
|
|
129
|
+
df = df[df["Altitude"] >= self.min_altitude]
|
|
130
|
+
|
|
131
|
+
# minutes to transit
|
|
132
|
+
ra_hours = df["RA"].to_numpy() * (24.0 / 360.0)
|
|
133
|
+
minutes = ((ra_hours - lst.hour) * u.hour) % (24 * u.hour)
|
|
134
|
+
mins = minutes.to_value(u.hour) * 60.0
|
|
135
|
+
df["Minutes to Transit"] = np.round(mins, 1)
|
|
136
|
+
df["Before/After Transit"] = np.where(df["Minutes to Transit"] > 720, "After", "Before")
|
|
137
|
+
df["Minutes to Transit"] = np.where(df["Minutes to Transit"] > 720,
|
|
138
|
+
1440 - df["Minutes to Transit"],
|
|
139
|
+
df["Minutes to Transit"])
|
|
140
|
+
|
|
141
|
+
# pick N nearest
|
|
142
|
+
df = df.nsmallest(self.object_limit, "Minutes to Transit")
|
|
143
|
+
self.calculation_complete.emit(df, "Calculation complete.")
|
|
144
|
+
except Exception as e:
|
|
145
|
+
self.calculation_complete.emit(pd.DataFrame(), f"Error: {e!s}")
|
|
146
|
+
|
|
147
|
+
def calculate_lunar_phase(self, t: Time, loc: EarthLocation):
|
|
148
|
+
moon = get_body("moon", t, loc)
|
|
149
|
+
sun = get_sun(t)
|
|
150
|
+
elong = moon.separation(sun).deg
|
|
151
|
+
|
|
152
|
+
phase_pct = int(round((1 - np.cos(np.radians(elong))) / 2 * 100))
|
|
153
|
+
|
|
154
|
+
future = t + (6 * u.hour)
|
|
155
|
+
is_waxing = get_body("moon", future, loc).separation(get_sun(future)).deg > elong
|
|
156
|
+
|
|
157
|
+
name = "new_moon.png"
|
|
158
|
+
if 0 <= elong < 9: name = "new_moon.png"
|
|
159
|
+
elif 9 <= elong < 18: name = "waxing_crescent_1.png" if is_waxing else "waning_crescent_5.png"
|
|
160
|
+
elif 18 <= elong < 27: name = "waxing_crescent_2.png" if is_waxing else "waning_crescent_4.png"
|
|
161
|
+
elif 27 <= elong < 36: name = "waxing_crescent_3.png" if is_waxing else "waning_crescent_3.png"
|
|
162
|
+
elif 36 <= elong < 45: name = "waxing_crescent_4.png" if is_waxing else "waning_crescent_2.png"
|
|
163
|
+
elif 45 <= elong < 54: name = "waxing_crescent_5.png" if is_waxing else "waning_crescent_1.png"
|
|
164
|
+
elif 54 <= elong < 90: name = "first_quarter.png"
|
|
165
|
+
elif 90 <= elong < 108: name = "waxing_gibbous_1.png" if is_waxing else "waning_gibbous_4.png"
|
|
166
|
+
elif 108 <= elong < 126: name = "waxing_gibbous_2.png" if is_waxing else "waning_gibbous_3.png"
|
|
167
|
+
elif 126 <= elong < 144: name = "waxing_gibbous_3.png" if is_waxing else "waning_gibbous_2.png"
|
|
168
|
+
elif 144 <= elong < 162: name = "waxing_gibbous_4.png" if is_waxing else "waning_gibbous_1.png"
|
|
169
|
+
elif 162 <= elong <= 180: name = "full_moon.png"
|
|
170
|
+
|
|
171
|
+
return phase_pct, name
|
|
172
|
+
|
|
173
|
+
|
|
174
|
+
# ---------------------------------------------------
|
|
175
|
+
# UI dialog
|
|
176
|
+
# ---------------------------------------------------
|
|
177
|
+
class SortableTreeWidgetItem(QTreeWidgetItem):
|
|
178
|
+
def __lt__(self, other):
|
|
179
|
+
col = self.treeWidget().sortColumn()
|
|
180
|
+
numeric_cols = [3, 4, 5, 7, 10] # Alt, Az, Minutes, Sep, Mag
|
|
181
|
+
if col in numeric_cols:
|
|
182
|
+
try:
|
|
183
|
+
return float(self.text(col)) < float(other.text(col))
|
|
184
|
+
except ValueError:
|
|
185
|
+
return self.text(col) < other.text(col)
|
|
186
|
+
return self.text(col) < other.text(col)
|
|
187
|
+
|
|
188
|
+
# ---------- coordinate parsing / formatting ----------
|
|
189
|
+
def _parse_deg_with_suffix(txt: str, kind: str) -> float:
|
|
190
|
+
"""
|
|
191
|
+
Parse latitude/longitude accepting:
|
|
192
|
+
30.1, -111, "30.1N", "111W", " -30.0 s ", etc.
|
|
193
|
+
kind: "lat" or "lon" (for range checks and suffix semantics)
|
|
194
|
+
Returns signed decimal degrees (E+, W-, N+, S-).
|
|
195
|
+
Raises ValueError on bad input.
|
|
196
|
+
"""
|
|
197
|
+
if txt is None:
|
|
198
|
+
raise ValueError("empty")
|
|
199
|
+
t = str(txt).strip().replace("°", "")
|
|
200
|
+
if not t:
|
|
201
|
+
raise ValueError("empty")
|
|
202
|
+
|
|
203
|
+
# extract trailing letter (N/S/E/W), case-insensitive
|
|
204
|
+
suffix = ""
|
|
205
|
+
if t and t[-1].upper() in ("N", "S", "E", "W"):
|
|
206
|
+
suffix = t[-1].upper()
|
|
207
|
+
t = t[:-1].strip()
|
|
208
|
+
|
|
209
|
+
val = float(t) # may be signed already
|
|
210
|
+
|
|
211
|
+
# apply suffix to sign if present
|
|
212
|
+
if suffix:
|
|
213
|
+
if kind == "lat":
|
|
214
|
+
if suffix == "N":
|
|
215
|
+
val = abs(val)
|
|
216
|
+
elif suffix == "S":
|
|
217
|
+
val = -abs(val)
|
|
218
|
+
else:
|
|
219
|
+
raise ValueError("Latitude suffix must be N or S")
|
|
220
|
+
elif kind == "lon":
|
|
221
|
+
if suffix == "E":
|
|
222
|
+
val = abs(val) # E is positive
|
|
223
|
+
elif suffix == "W":
|
|
224
|
+
val = -abs(val) # W is negative
|
|
225
|
+
else:
|
|
226
|
+
raise ValueError("Longitude suffix must be E or W")
|
|
227
|
+
|
|
228
|
+
# clamp / validate ranges
|
|
229
|
+
if kind == "lat":
|
|
230
|
+
if not (-90.0 <= val <= 90.0):
|
|
231
|
+
raise ValueError("Latitude must be in [-90, 90]")
|
|
232
|
+
else:
|
|
233
|
+
if not (-180.0 <= val <= 180.0):
|
|
234
|
+
raise ValueError("Longitude must be in [-180, 180]")
|
|
235
|
+
|
|
236
|
+
return val
|
|
237
|
+
|
|
238
|
+
|
|
239
|
+
def _format_with_suffix(val: float, kind: str) -> str:
|
|
240
|
+
"""
|
|
241
|
+
Render signed degrees with hemisphere suffix.
|
|
242
|
+
e.g. lat -33.5 -> '33.5S'
|
|
243
|
+
lon -111 -> '111W'
|
|
244
|
+
"""
|
|
245
|
+
v = float(val)
|
|
246
|
+
if kind == "lat":
|
|
247
|
+
hemi = "N" if v >= 0 else "S"
|
|
248
|
+
else:
|
|
249
|
+
hemi = "E" if v >= 0 else "W"
|
|
250
|
+
return f"{abs(v):g}{hemi}"
|
|
251
|
+
|
|
252
|
+
def _tz_vs_longitude_hint(tz_name: str, date_str: str, time_str: str, lon_deg: float):
|
|
253
|
+
"""
|
|
254
|
+
Compare timezone UTC offset to longitude.
|
|
255
|
+
Heuristic:
|
|
256
|
+
• sign check: West longitudes (~W) usually have negative UTC offsets; East longitudes (~E) positive
|
|
257
|
+
• central meridian check: |lon| should be near |offset_hours*15|; flag if > 45°
|
|
258
|
+
Returns (should_warn: bool, human_msg: str, utc_str: str, central_meridian: float)
|
|
259
|
+
"""
|
|
260
|
+
try:
|
|
261
|
+
local_tz = pytz.timezone(tz_name)
|
|
262
|
+
naive = datetime.strptime(f"{date_str} {time_str}", "%Y-%m-%d %H:%M")
|
|
263
|
+
local_dt = local_tz.localize(naive)
|
|
264
|
+
off_hours = (local_dt.utcoffset() or pd.Timedelta(0)).total_seconds() / 3600.0
|
|
265
|
+
except Exception:
|
|
266
|
+
return (False, "", "", 0.0)
|
|
267
|
+
|
|
268
|
+
# UTC string like UTC−7 or UTC+5:30
|
|
269
|
+
hours = int(off_hours)
|
|
270
|
+
mins = int(round(abs(off_hours - hours) * 60))
|
|
271
|
+
sign = "−" if off_hours < 0 else "+"
|
|
272
|
+
if mins:
|
|
273
|
+
utc_str = f"UTC{sign}{abs(hours)}:{mins:02d}"
|
|
274
|
+
else:
|
|
275
|
+
utc_str = f"UTC{sign}{abs(hours)}"
|
|
276
|
+
|
|
277
|
+
central = off_hours * 15.0 # “central meridian” for that offset
|
|
278
|
+
sign_ok = (abs(off_hours) < 1e-9) or (lon_deg == 0) or ((lon_deg > 0) == (off_hours > 0))
|
|
279
|
+
far = abs(abs(lon_deg) - abs(central)) > 45.0
|
|
280
|
+
|
|
281
|
+
if (not sign_ok) or far:
|
|
282
|
+
msg = (f"Timezone {tz_name} ({utc_str}) looks inconsistent with longitude "
|
|
283
|
+
f"{abs(lon_deg):g}{'E' if lon_deg>0 else 'W'} "
|
|
284
|
+
f"(central meridian ≈ {abs(central):.0f}°{'E' if central>0 else 'W'}).")
|
|
285
|
+
return (True, msg, utc_str, central)
|
|
286
|
+
return (False, "", utc_str, central)
|
|
287
|
+
|
|
288
|
+
|
|
289
|
+
class WhatsInMySkyDialog(QDialog):
|
|
290
|
+
def __init__(self, parent=None, wims_path: Optional[str] = None, wrench_path: Optional[str] = None):
|
|
291
|
+
super().__init__(parent)
|
|
292
|
+
self.setWindowTitle("What's In My Sky")
|
|
293
|
+
if wims_path:
|
|
294
|
+
self.setWindowIcon(QIcon(wims_path))
|
|
295
|
+
|
|
296
|
+
self.settings = QSettings()
|
|
297
|
+
self.object_limit = int(self.settings.value("object_limit", 100, int))
|
|
298
|
+
|
|
299
|
+
self._build_ui(wrench_path)
|
|
300
|
+
self._load_settings_into_ui()
|
|
301
|
+
|
|
302
|
+
self.calc_thread: Optional[CalculationThread] = None
|
|
303
|
+
self.catalog_file: Optional[str] = None
|
|
304
|
+
|
|
305
|
+
# ---------- UI ----------
|
|
306
|
+
def _build_ui(self, wrench_path: Optional[str]):
|
|
307
|
+
layout = QGridLayout(self)
|
|
308
|
+
fixed_w = 150
|
|
309
|
+
|
|
310
|
+
self.latitude_entry = QLineEdit(); self.latitude_entry.setFixedWidth(fixed_w)
|
|
311
|
+
self.longitude_entry = QLineEdit(); self.longitude_entry.setFixedWidth(fixed_w)
|
|
312
|
+
self.date_entry = QLineEdit(); self.date_entry.setFixedWidth(fixed_w)
|
|
313
|
+
self.time_entry = QLineEdit(); self.time_entry.setFixedWidth(fixed_w)
|
|
314
|
+
|
|
315
|
+
self.timezone_combo = QComboBox(); self.timezone_combo.addItems(pytz.all_timezones)
|
|
316
|
+
self.timezone_combo.setFixedWidth(fixed_w)
|
|
317
|
+
|
|
318
|
+
r = 0
|
|
319
|
+
layout.addWidget(QLabel("Latitude:"), r, 0); layout.addWidget(self.latitude_entry, r, 1); r += 1
|
|
320
|
+
layout.addWidget(QLabel("Longitude (E+, W−):"), r, 0); layout.addWidget(self.longitude_entry, r, 1); r += 1
|
|
321
|
+
layout.addWidget(QLabel("Date (YYYY-MM-DD):"), r, 0); layout.addWidget(self.date_entry, r, 1); r += 1
|
|
322
|
+
layout.addWidget(QLabel("Time (HH:MM):"), r, 0); layout.addWidget(self.time_entry, r, 1); r += 1
|
|
323
|
+
layout.addWidget(QLabel("Time Zone:"), r, 0); layout.addWidget(self.timezone_combo, r, 1); r += 1
|
|
324
|
+
|
|
325
|
+
self.min_altitude_entry = QLineEdit(); self.min_altitude_entry.setFixedWidth(fixed_w)
|
|
326
|
+
layout.addWidget(QLabel("Min Altitude (0–90°):"), r, 0); layout.addWidget(self.min_altitude_entry, r, 1); r += 1
|
|
327
|
+
|
|
328
|
+
# catalogs
|
|
329
|
+
catalog_frame = QScrollArea()
|
|
330
|
+
cat_widget = QWidget(); cat_layout = QGridLayout(cat_widget)
|
|
331
|
+
self.catalog_vars: dict[str, QCheckBox] = {}
|
|
332
|
+
for i, name in enumerate(["Messier","NGC","IC","Caldwell","Abell","Sharpless","LBN","LDN","PNG","User"]):
|
|
333
|
+
cb = QCheckBox(name); cb.setChecked(False)
|
|
334
|
+
cat_layout.addWidget(cb, i // 5, i % 5)
|
|
335
|
+
self.catalog_vars[name] = cb
|
|
336
|
+
catalog_frame.setWidget(cat_widget); catalog_frame.setFixedWidth(fixed_w + 250)
|
|
337
|
+
layout.addWidget(QLabel("Catalog Filters:"), r, 0); layout.addWidget(catalog_frame, r, 1); r += 1
|
|
338
|
+
|
|
339
|
+
# RA/Dec format
|
|
340
|
+
self.ra_dec_degrees = QRadioButton("Degrees")
|
|
341
|
+
self.ra_dec_hms = QRadioButton("H:M:S / D:M:S")
|
|
342
|
+
self.ra_dec_degrees.setChecked(True)
|
|
343
|
+
g = QButtonGroup(self); g.addButton(self.ra_dec_degrees); g.addButton(self.ra_dec_hms)
|
|
344
|
+
ra_row = QHBoxLayout(); ra_row.addWidget(self.ra_dec_degrees); ra_row.addWidget(self.ra_dec_hms)
|
|
345
|
+
layout.addWidget(QLabel("RA/Dec Format:"), r, 0); layout.addLayout(ra_row, r, 1); r += 1
|
|
346
|
+
self.ra_dec_degrees.toggled.connect(self.update_ra_dec_format)
|
|
347
|
+
self.ra_dec_hms.toggled.connect(self.update_ra_dec_format)
|
|
348
|
+
|
|
349
|
+
# action buttons / status
|
|
350
|
+
calc_btn = QPushButton("Calculate"); calc_btn.setFixedWidth(fixed_w); calc_btn.clicked.connect(self.start_calculation)
|
|
351
|
+
layout.addWidget(calc_btn, r, 0); r += 1
|
|
352
|
+
|
|
353
|
+
self.status_label = QLabel("Status: Idle"); layout.addWidget(self.status_label, r, 0, 1, 2); r += 1
|
|
354
|
+
self.lst_label = QLabel("Local Sidereal Time: 0.000"); layout.addWidget(self.lst_label, r, 0, 1, 2); r += 1
|
|
355
|
+
|
|
356
|
+
# moon phase preview
|
|
357
|
+
self.lunar_phase_image_label = QLabel()
|
|
358
|
+
layout.addWidget(self.lunar_phase_image_label, 0, 2, 4, 1)
|
|
359
|
+
self.lunar_phase_label = QLabel("Lunar Phase: N/A")
|
|
360
|
+
layout.addWidget(self.lunar_phase_label, 4, 2)
|
|
361
|
+
|
|
362
|
+
# results tree
|
|
363
|
+
self.tree = QTreeWidget()
|
|
364
|
+
self.tree.setHeaderLabels([
|
|
365
|
+
"Name","RA","Dec","Altitude","Azimuth","Minutes to Transit","Before/After Transit",
|
|
366
|
+
"Degrees from Moon","Alt Name","Type","Magnitude","Size (arcmin)"
|
|
367
|
+
])
|
|
368
|
+
self.tree.setSortingEnabled(True)
|
|
369
|
+
hdr = self.tree.header()
|
|
370
|
+
hdr.setSectionResizeMode(QHeaderView.ResizeMode.Interactive)
|
|
371
|
+
hdr.setStretchLastSection(False)
|
|
372
|
+
self.tree.sortByColumn(5, Qt.SortOrder.AscendingOrder)
|
|
373
|
+
self.tree.itemDoubleClicked.connect(self.on_row_double_click)
|
|
374
|
+
layout.addWidget(self.tree, r, 0, 1, 3); r += 1
|
|
375
|
+
|
|
376
|
+
# bottom row
|
|
377
|
+
add_btn = QPushButton("Add Custom Object"); add_btn.setFixedWidth(fixed_w); add_btn.clicked.connect(self.add_custom_object)
|
|
378
|
+
layout.addWidget(add_btn, r, 0)
|
|
379
|
+
|
|
380
|
+
save_btn = QPushButton("Save to CSV"); save_btn.setFixedWidth(fixed_w); save_btn.clicked.connect(self.save_to_csv)
|
|
381
|
+
layout.addWidget(save_btn, r, 1)
|
|
382
|
+
|
|
383
|
+
settings_btn = QPushButton(); settings_btn.setFixedWidth(fixed_w)
|
|
384
|
+
if wrench_path and os.path.exists(wrench_path):
|
|
385
|
+
settings_btn.setIcon(QIcon(wrench_path))
|
|
386
|
+
settings_btn.clicked.connect(self.open_settings)
|
|
387
|
+
layout.addWidget(settings_btn, r, 2)
|
|
388
|
+
|
|
389
|
+
layout.setColumnStretch(2, 1)
|
|
390
|
+
|
|
391
|
+
# ---------- settings ----------
|
|
392
|
+
def _load_settings_into_ui(self):
|
|
393
|
+
def cast(v, typ, default):
|
|
394
|
+
try: return typ(v)
|
|
395
|
+
except Exception: return default
|
|
396
|
+
lat = cast(self.settings.value("latitude", 0.0), float, 0.0)
|
|
397
|
+
lon = cast(self.settings.value("longitude", 0.0), float, 0.0)
|
|
398
|
+
date = self.settings.value("date", datetime.now().strftime("%Y-%m-%d"))
|
|
399
|
+
time = self.settings.value("time", "00:00")
|
|
400
|
+
tz = self.settings.value("timezone", "UTC")
|
|
401
|
+
min_alt = cast(self.settings.value("min_altitude", 0.0), float, 0.0)
|
|
402
|
+
self.object_limit = cast(self.settings.value("object_limit", 100), int, 100)
|
|
403
|
+
|
|
404
|
+
self.latitude_entry.setText(str(lat))
|
|
405
|
+
self.longitude_entry.setText(str(lon))
|
|
406
|
+
self.date_entry.setText(date)
|
|
407
|
+
self.time_entry.setText(time)
|
|
408
|
+
self.timezone_combo.setCurrentText(tz)
|
|
409
|
+
self.min_altitude_entry.setText(str(min_alt))
|
|
410
|
+
|
|
411
|
+
def _save_settings(self, latitude, longitude, date, time, timezone, min_altitude):
|
|
412
|
+
self.settings.setValue("latitude", latitude)
|
|
413
|
+
self.settings.setValue("longitude", longitude)
|
|
414
|
+
self.settings.setValue("date", date)
|
|
415
|
+
self.settings.setValue("time", time)
|
|
416
|
+
self.settings.setValue("timezone", timezone)
|
|
417
|
+
self.settings.setValue("min_altitude", min_altitude)
|
|
418
|
+
|
|
419
|
+
# ---------- actions ----------
|
|
420
|
+
def start_calculation(self):
|
|
421
|
+
try:
|
|
422
|
+
orig_lat_txt = self.latitude_entry.text()
|
|
423
|
+
orig_lon_txt = self.longitude_entry.text()
|
|
424
|
+
|
|
425
|
+
latitude = _parse_deg_with_suffix(orig_lat_txt, "lat")
|
|
426
|
+
longitude = _parse_deg_with_suffix(orig_lon_txt, "lon")
|
|
427
|
+
|
|
428
|
+
# Pretty-print back with suffixes
|
|
429
|
+
self.latitude_entry.setText(_format_with_suffix(latitude, "lat"))
|
|
430
|
+
self.longitude_entry.setText(_format_with_suffix(longitude, "lon"))
|
|
431
|
+
|
|
432
|
+
date_str = self.date_entry.text().strip()
|
|
433
|
+
time_str = self.time_entry.text().strip()
|
|
434
|
+
tz_str = self.timezone_combo.currentText()
|
|
435
|
+
min_alt = float(self.min_altitude_entry.text())
|
|
436
|
+
except ValueError as e:
|
|
437
|
+
self.update_status(f"Invalid input: {e}")
|
|
438
|
+
return
|
|
439
|
+
|
|
440
|
+
# Heuristic warning (and gentle auto-fix if user probably forgot the suffix)
|
|
441
|
+
warn, msg, utc_str, central = _tz_vs_longitude_hint(tz_str, date_str, time_str, longitude)
|
|
442
|
+
if warn:
|
|
443
|
+
# If the user typed a bare number (no N/S/E/W) and sign mismatches TZ, suggest flip
|
|
444
|
+
bare_lon = (orig_lon_txt.strip() and orig_lon_txt.strip()[-1].upper() not in ("E","W"))
|
|
445
|
+
sign_mismatch = not ((longitude > 0) == (central > 0) or abs(central) < 1e-6 or longitude == 0)
|
|
446
|
+
|
|
447
|
+
if bare_lon and sign_mismatch:
|
|
448
|
+
# Flip once, write back, and tell the user.
|
|
449
|
+
longitude = -longitude
|
|
450
|
+
self.longitude_entry.setText(_format_with_suffix(longitude, "lon"))
|
|
451
|
+
self.update_status(f"{msg} → Assuming you meant {_format_with_suffix(longitude, 'lon')} (auto-corrected).")
|
|
452
|
+
else:
|
|
453
|
+
self.update_status(msg + " Please verify your longitude/timezone.")
|
|
454
|
+
else:
|
|
455
|
+
self.update_status("Inputs look consistent.")
|
|
456
|
+
|
|
457
|
+
# Persist settings (numeric)
|
|
458
|
+
self._save_settings(latitude, longitude, date_str, time_str, tz_str, min_alt)
|
|
459
|
+
|
|
460
|
+
catalogs = [name for name, cb in self.catalog_vars.items() if cb.isChecked()]
|
|
461
|
+
self.calc_thread = CalculationThread(latitude, longitude, date_str, time_str, tz_str,
|
|
462
|
+
min_alt, catalogs, self.object_limit)
|
|
463
|
+
self.catalog_file = self.calc_thread.catalog_file
|
|
464
|
+
|
|
465
|
+
self.calc_thread.calculation_complete.connect(self.on_calculation_complete)
|
|
466
|
+
self.calc_thread.lunar_phase_calculated.connect(self.update_lunar_phase)
|
|
467
|
+
self.calc_thread.lst_calculated.connect(self.update_lst)
|
|
468
|
+
self.calc_thread.status_update.connect(self.update_status)
|
|
469
|
+
|
|
470
|
+
self.update_status("Calculating…")
|
|
471
|
+
self.calc_thread.start()
|
|
472
|
+
|
|
473
|
+
def update_lunar_phase(self, phase_percentage: int, phase_image_name: str):
|
|
474
|
+
self.lunar_phase_label.setText(f"Lunar Phase: {phase_percentage}% illuminated")
|
|
475
|
+
pth = imgs_path(phase_image_name)
|
|
476
|
+
if os.path.exists(pth):
|
|
477
|
+
pm = QPixmap(pth).scaled(100, 100, Qt.AspectRatioMode.KeepAspectRatio,
|
|
478
|
+
Qt.TransformationMode.SmoothTransformation)
|
|
479
|
+
self.lunar_phase_image_label.setPixmap(pm)
|
|
480
|
+
|
|
481
|
+
def on_calculation_complete(self, df: pd.DataFrame, message: str):
|
|
482
|
+
self.update_status(message)
|
|
483
|
+
self.tree.clear()
|
|
484
|
+
if df.empty:
|
|
485
|
+
return
|
|
486
|
+
for _, row in df.iterrows():
|
|
487
|
+
ra_disp, dec_disp = row["RA"], row["Dec"]
|
|
488
|
+
if self.ra_dec_hms.isChecked():
|
|
489
|
+
sc = SkyCoord(ra=row["RA"] * u.deg, dec=row["Dec"] * u.deg)
|
|
490
|
+
ra_disp = sc.ra.to_string(unit=u.hour, sep=":")
|
|
491
|
+
dec_disp = sc.dec.to_string(unit=u.deg, sep=":")
|
|
492
|
+
size_arcmin = row.get("Info", "")
|
|
493
|
+
if pd.notna(size_arcmin):
|
|
494
|
+
size_arcmin = str(size_arcmin)
|
|
495
|
+
vals = [
|
|
496
|
+
str(row.get("Name","") or ""),
|
|
497
|
+
str(ra_disp),
|
|
498
|
+
str(dec_disp),
|
|
499
|
+
str(row.get("Altitude","")),
|
|
500
|
+
str(row.get("Azimuth","")),
|
|
501
|
+
str(int(row.get("Minutes to Transit",0))) if pd.notna(row.get("Minutes to Transit", np.nan)) else "",
|
|
502
|
+
str(row.get("Before/After Transit","")),
|
|
503
|
+
str(round(row.get("Degrees from Moon", 0.0), 2)) if pd.notna(row.get("Degrees from Moon", np.nan)) else "",
|
|
504
|
+
row.get("Alt Name","") if pd.notna(row.get("Alt Name","")) else "",
|
|
505
|
+
row.get("Type","") if pd.notna(row.get("Type","")) else "",
|
|
506
|
+
str(row.get("Magnitude","")) if pd.notna(row.get("Magnitude","")) else "",
|
|
507
|
+
str(size_arcmin) if pd.notna(size_arcmin) else "",
|
|
508
|
+
]
|
|
509
|
+
self.tree.addTopLevelItem(SortableTreeWidgetItem(vals))
|
|
510
|
+
|
|
511
|
+
def update_status(self, msg: str):
|
|
512
|
+
self.status_label.setText(f"Status: {msg}")
|
|
513
|
+
|
|
514
|
+
def update_lst(self, msg: str):
|
|
515
|
+
self.lst_label.setText(msg)
|
|
516
|
+
|
|
517
|
+
def open_settings(self):
|
|
518
|
+
n, ok = QInputDialog.getInt(self, "Settings", "Enter number of objects to display:",
|
|
519
|
+
value=int(self.object_limit), min=1, max=1000)
|
|
520
|
+
if ok:
|
|
521
|
+
self.object_limit = int(n)
|
|
522
|
+
self.settings.setValue("object_limit", int(n))
|
|
523
|
+
|
|
524
|
+
def on_row_double_click(self, item: QTreeWidgetItem, column: int):
|
|
525
|
+
name = item.text(0).replace(" ", "")
|
|
526
|
+
webbrowser.open(f"https://www.astrobin.com/search/?q={name}")
|
|
527
|
+
|
|
528
|
+
def add_custom_object(self):
|
|
529
|
+
name, ok = QInputDialog.getText(self, "Add Custom Object", "Enter object name:")
|
|
530
|
+
if not ok or not name:
|
|
531
|
+
return
|
|
532
|
+
ra, ok = QInputDialog.getDouble(self, "Add Custom Object", "Enter RA (deg):", decimals=3)
|
|
533
|
+
if not ok: return
|
|
534
|
+
dec, ok = QInputDialog.getDouble(self, "Add Custom Object", "Enter Dec (deg):", decimals=3)
|
|
535
|
+
if not ok: return
|
|
536
|
+
|
|
537
|
+
entry = {"Name": name, "RA": ra, "Dec": dec, "Catalog": "User",
|
|
538
|
+
"Alt Name": "User Defined", "Type": "Custom", "Magnitude": "", "Info": ""}
|
|
539
|
+
|
|
540
|
+
catalog_csv = self.catalog_file or os.path.join(os.path.expanduser("~"), "celestial_catalog.csv")
|
|
541
|
+
try:
|
|
542
|
+
df = pd.read_csv(catalog_csv, encoding="ISO-8859-1") if os.path.exists(catalog_csv) else pd.DataFrame()
|
|
543
|
+
df = pd.concat([df, pd.DataFrame([entry])], ignore_index=True)
|
|
544
|
+
df.to_csv(catalog_csv, index=False, encoding="ISO-8859-1")
|
|
545
|
+
self.update_status(f"Added custom object: {name}")
|
|
546
|
+
except Exception as e:
|
|
547
|
+
QMessageBox.warning(self, "Add Custom Object", f"Could not update catalog:\n{e}")
|
|
548
|
+
|
|
549
|
+
def update_ra_dec_format(self):
|
|
550
|
+
use_deg = self.ra_dec_degrees.isChecked()
|
|
551
|
+
for i in range(self.tree.topLevelItemCount()):
|
|
552
|
+
it = self.tree.topLevelItem(i)
|
|
553
|
+
ra_txt, dec_txt = it.text(1), it.text(2)
|
|
554
|
+
try:
|
|
555
|
+
if use_deg:
|
|
556
|
+
if ":" in ra_txt:
|
|
557
|
+
sc = SkyCoord(ra=ra_txt, dec=dec_txt, unit=(u.hourangle, u.deg))
|
|
558
|
+
it.setText(1, f"{sc.ra.deg:.3f}")
|
|
559
|
+
it.setText(2, f"{sc.dec.deg:.3f}")
|
|
560
|
+
else:
|
|
561
|
+
if ":" not in ra_txt:
|
|
562
|
+
sc = SkyCoord(ra=float(ra_txt) * u.deg, dec=float(dec_txt) * u.deg)
|
|
563
|
+
it.setText(1, sc.ra.to_string(unit=u.hour, sep=":"))
|
|
564
|
+
it.setText(2, sc.dec.to_string(unit=u.deg, sep=":"))
|
|
565
|
+
except Exception:
|
|
566
|
+
pass
|
|
567
|
+
|
|
568
|
+
def save_to_csv(self):
|
|
569
|
+
path, _ = QFileDialog.getSaveFileName(self, "Save CSV File", "", "CSV files (*.csv);;All Files (*)")
|
|
570
|
+
if not path:
|
|
571
|
+
return
|
|
572
|
+
cols = [self.tree.headerItem().text(i) for i in range(self.tree.columnCount())]
|
|
573
|
+
rows = []
|
|
574
|
+
for i in range(self.tree.topLevelItemCount()):
|
|
575
|
+
it = self.tree.topLevelItem(i)
|
|
576
|
+
rows.append([it.text(j) for j in range(self.tree.columnCount())])
|
|
577
|
+
pd.DataFrame(rows, columns=cols).to_csv(path, index=False)
|
|
578
|
+
self.update_status(f"Data saved to {path}")
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: setiastrosuitepro
|
|
3
|
-
Version: 1.6.1
|
|
3
|
+
Version: 1.6.1.post1
|
|
4
4
|
Summary: Seti Astro Suite Pro - Advanced astrophotography toolkit for image calibration, stacking, registration, photometry, and visualization
|
|
5
5
|
License: GPL-3.0
|
|
6
6
|
License-File: LICENSE
|
|
@@ -141,9 +141,9 @@ setiastro/images/wims.png,sha256=EFFaRRUPMMPInRXSr_thUGQAxZTXgDVkjHeiaJiZmgk,117
|
|
|
141
141
|
setiastro/images/wrench_icon.png,sha256=4-DFNkXZUBkfK4WCFLdcr1-b3MsrYcE3DGrp4tL6AVI,72352
|
|
142
142
|
setiastro/images/xisfliberator.png,sha256=sPC7mPHX_ToyBh9nSLn9B5fVRaM89QvqzW30ohbW8VE,1551111
|
|
143
143
|
setiastro/saspro/__init__.py,sha256=C6Puq5PQr3n0aJ-2i_YAaUhTjPI-pcSzie3_tij3hhw,608
|
|
144
|
-
setiastro/saspro/__main__.py,sha256=
|
|
144
|
+
setiastro/saspro/__main__.py,sha256=go6yDAWnm6sfbRazTCenmREkRhIVhqhGrJpHVAE02Zc,31919
|
|
145
145
|
setiastro/saspro/_generated/__init__.py,sha256=HbruQfKNbbVL4kh_t4oVG3UeUieaW8MUaqIcDCmnTvA,197
|
|
146
|
-
setiastro/saspro/_generated/build_info.py,sha256=
|
|
146
|
+
setiastro/saspro/_generated/build_info.py,sha256=6XAVIml6EFBtETDl-OEnI3HIfr25e04VdZk3FiB-Fmk,88
|
|
147
147
|
setiastro/saspro/abe.py,sha256=v6ldLLtdCfRsaR2sNqL8bl5duMSDwl_7yKR1wOV_Oro,56418
|
|
148
148
|
setiastro/saspro/abe_preset.py,sha256=u9t16yTb9v98tLjhvh496Fsp3Z-dNiBSs5itnAaJwh8,7750
|
|
149
149
|
setiastro/saspro/aberration_ai.py,sha256=DTwzlQYOzmFdCwlLxAqivAOgUJN-T6TEMJYhWeIEKN8,26651
|
|
@@ -196,7 +196,7 @@ setiastro/saspro/ghs_preset.py,sha256=Zw3MJH5rEz7nqLdlmRBm3vYXgyopoupyDGAhM-PRXq
|
|
|
196
196
|
setiastro/saspro/graxpert.py,sha256=XzdFqmE9P5gbXaDtZiIONQbpowHHKDtwSNOpMevsw34,22431
|
|
197
197
|
setiastro/saspro/graxpert_preset.py,sha256=ESds2NPMPfsBHWNBfyYZ1rFpQxZ9gPOtxwz8enHfFfc,10719
|
|
198
198
|
setiastro/saspro/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
199
|
-
setiastro/saspro/gui/main_window.py,sha256=
|
|
199
|
+
setiastro/saspro/gui/main_window.py,sha256=uL9BbQb6GqucLHuw_uRuVfNPwxG-O4rItdRQAMAMe8g,347225
|
|
200
200
|
setiastro/saspro/gui/mixins/__init__.py,sha256=ubdTIri0xoRs6MwDnEaVsAHbMxuPqz0CZcYcue3Mkcc,836
|
|
201
201
|
setiastro/saspro/gui/mixins/dock_mixin.py,sha256=uYwQ1n0a0Ta8gf_bYtUoleTBz0t5Drx7htEIptItUw8,10400
|
|
202
202
|
setiastro/saspro/gui/mixins/file_mixin.py,sha256=IRI_6MNYwe9zn4nVpvoc_y4zlCIh1eEiueOIRo5IlVY,16757
|
|
@@ -262,7 +262,7 @@ setiastro/saspro/pedestal.py,sha256=WkwNaY0Qp1XP6KFtb6tnnKMhg71R5WEhVOdwWwJFmJ8,
|
|
|
262
262
|
setiastro/saspro/perfect_palette_picker.py,sha256=kfc5wPTXpYKByQGk3OxEQRnA3FtYqtWWYMrM4AHfch0,45119
|
|
263
263
|
setiastro/saspro/pipeline.py,sha256=QO8tttr050dbJkpkShO4g7gUEESAcRhhn7LLp0IwKyw,3774
|
|
264
264
|
setiastro/saspro/pixelmath.py,sha256=rF0kE0OPHFT_r9zcPj_VbT1fNHwxyvp4goB9YSPyP2k,72014
|
|
265
|
-
setiastro/saspro/plate_solver.py,sha256=
|
|
265
|
+
setiastro/saspro/plate_solver.py,sha256=MQOF3v8rmWkO51aeEcJsxmZy3fhonkHUlWaNlf1uJYk,95923
|
|
266
266
|
setiastro/saspro/project_io.py,sha256=usCbXxgB6bKj4L_71eTQIIzOzYAWxaNUoY4duB6oC5g,30357
|
|
267
267
|
setiastro/saspro/psf_utils.py,sha256=K3R4BjUzsopLRuLTbPlm8lBw3-x9twNWG4bK-4phDM4,4555
|
|
268
268
|
setiastro/saspro/psf_viewer.py,sha256=0R6ImAHsJl2za5bcsQC7lQ3v2CHyDsRUICOkNmq4JpE,20548
|
|
@@ -316,7 +316,7 @@ setiastro/saspro/translations/saspro_pt.ts,sha256=cN3bm7SQP0Xt1nZ6izYpNlxxYWVtBG
|
|
|
316
316
|
setiastro/saspro/translations/saspro_zh.qm,sha256=db_Izt2ypH3EtculiaRGL9GCV_nkspK9ZJZ6CYsKnoY,357316
|
|
317
317
|
setiastro/saspro/translations/saspro_zh.ts,sha256=3h_6b_Al7Ario8VqP6j8Lq20aTWeUACD0PTz1Z7FIpc,680998
|
|
318
318
|
setiastro/saspro/translations/zh_translations.py,sha256=ClCR41kMlHEWgtlzm34mqkDqIG3--TIGnwrL88cCQos,222559
|
|
319
|
-
setiastro/saspro/versioning.py,sha256=
|
|
319
|
+
setiastro/saspro/versioning.py,sha256=gmFcpSc2fEMINaI-oRWmnmFkCjKNdc9RviqCp8I4vJw,1237
|
|
320
320
|
setiastro/saspro/view_bundle.py,sha256=VbfjNMX4Zt0zpTVlvvWfuP2j_MBbjVdoRVBRd4Pfcew,60300
|
|
321
321
|
setiastro/saspro/wavescale_hdr.py,sha256=dqpHP3WvPzvGCh3eSFvIJCWUumio9sG3HbsZMVPAzo4,26571
|
|
322
322
|
setiastro/saspro/wavescale_hdr_preset.py,sha256=ertUDFHBYmkZf2vyucE70dzgr3M_lSoXKRih36ZsdzI,3749
|
|
@@ -332,11 +332,13 @@ setiastro/saspro/widgets/preview_dialogs.py,sha256=qt2bRgLBD_a0sGb0t4losYFMAFC-Z
|
|
|
332
332
|
setiastro/saspro/widgets/spinboxes.py,sha256=pnwuy1tYOCSxcX1tD0a4CyBle6mOuRrv6wgev47pmUk,9970
|
|
333
333
|
setiastro/saspro/widgets/themed_buttons.py,sha256=m6f_AYCx-tUmKf3U1CBuBbUsXOLW3cS3_oZUd4plVuU,445
|
|
334
334
|
setiastro/saspro/widgets/wavelet_utils.py,sha256=mlPO1lCVkxJsX1ESOX6QunjH8yM0-s4pt4X9TOe9haE,10327
|
|
335
|
+
setiastro/saspro/wimi.py,sha256=qlWJc1GYzFJfffNEXFJd5OHYSZxjv2JnVYm-6aA_3BM,340944
|
|
336
|
+
setiastro/saspro/wims.py,sha256=aO8PJiDO1dKysG2Efdp7zlFY5n6FGP3WKwmrAjjAAiA,26641
|
|
335
337
|
setiastro/saspro/window_shelf.py,sha256=SAe243tCjb1LuNSFupimHAF-TbKtrwQ88z10zrVtk1I,7435
|
|
336
338
|
setiastro/saspro/xisf.py,sha256=Rh1XTKjycEZ2WR6Z7D0uQGVQFwjCeLSFKSJq5JOhCxI,49702
|
|
337
|
-
setiastrosuitepro-1.6.1.dist-info/entry_points.txt,sha256=vJfnZaV6Uj3laETakqT8-D-aJZI_nYIicrhSoL0uuko,227
|
|
338
|
-
setiastrosuitepro-1.6.1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
339
|
-
setiastrosuitepro-1.6.1.dist-info/licenses/license.txt,sha256=KCwYZ9VpVwmzjelDq1BzzWqpBvt9nbbapa-woz47hfQ,123930
|
|
340
|
-
setiastrosuitepro-1.6.1.dist-info/METADATA,sha256=
|
|
341
|
-
setiastrosuitepro-1.6.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
342
|
-
setiastrosuitepro-1.6.1.dist-info/RECORD,,
|
|
339
|
+
setiastrosuitepro-1.6.1.post1.dist-info/entry_points.txt,sha256=vJfnZaV6Uj3laETakqT8-D-aJZI_nYIicrhSoL0uuko,227
|
|
340
|
+
setiastrosuitepro-1.6.1.post1.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
|
|
341
|
+
setiastrosuitepro-1.6.1.post1.dist-info/licenses/license.txt,sha256=KCwYZ9VpVwmzjelDq1BzzWqpBvt9nbbapa-woz47hfQ,123930
|
|
342
|
+
setiastrosuitepro-1.6.1.post1.dist-info/METADATA,sha256=F3VM06MS9FMKhuXSt-MQE_NhtP_4N2Y_yGAMCAbaixo,8911
|
|
343
|
+
setiastrosuitepro-1.6.1.post1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
344
|
+
setiastrosuitepro-1.6.1.post1.dist-info/RECORD,,
|
|
File without changes
|
{setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/entry_points.txt
RENAMED
|
File without changes
|
{setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/licenses/LICENSE
RENAMED
|
File without changes
|
{setiastrosuitepro-1.6.1.dist-info → setiastrosuitepro-1.6.1.post1.dist-info}/licenses/license.txt
RENAMED
|
File without changes
|