setiastrosuitepro 1.8.2__py3-none-any.whl → 1.8.3__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.

Potentially problematic release.


This version of setiastrosuitepro might be problematic. Click here for more details.

@@ -34,7 +34,6 @@ from setiastro.saspro.widgets.image_utils import extract_mask_from_document as _
34
34
  _MAD_NORM = 1.4826
35
35
 
36
36
  # --------- deterministic, invertible stretch used for StarNet ----------
37
- # ---------- Siril-like MTF (linked) pre-stretch for StarNet ----------
38
37
  def _robust_peak_sigma(gray: np.ndarray) -> tuple[float, float]:
39
38
  gray = gray.astype(np.float32, copy=False)
40
39
  med = float(np.median(gray))
@@ -61,18 +60,6 @@ def _mtf_apply(x: np.ndarray, shadows: float, midtones: float, highlights: float
61
60
  return np.clip(y, 0.0, 1.0).astype(np.float32, copy=False)
62
61
 
63
62
  def _mtf_inverse(y: np.ndarray, shadows: float, midtones: float, highlights: float) -> np.ndarray:
64
- """
65
- Pseudoinverse of MTF, matching Siril's MTF_pseudoinverse() implementation.
66
-
67
- C reference:
68
-
69
- float MTF_pseudoinverse(float y, struct mtf_params params) {
70
- return ((((params.shadows + params.highlights) * params.midtones
71
- - params.shadows) * y - params.shadows * params.midtones
72
- + params.shadows)
73
- / ((2 * params.midtones - 1) * y - params.midtones + 1));
74
- }
75
- """
76
63
  s = float(shadows)
77
64
  m = float(midtones)
78
65
  h = float(highlights)
@@ -107,7 +94,7 @@ def _mtf_params_linked(img_rgb01: np.ndarray, shadowclip_sigma: float = -2.8, ta
107
94
  s = peak + shadowclip_sigma * sigma
108
95
  # keep [0..1) with margin
109
96
  s = float(np.clip(s, gray.min(), max(gray.max() - 1e-6, 0.0)))
110
- h = 1.0 # Siril effectively normalizes to <=1 before 16-bit TIFF
97
+ h = 1.0
111
98
  # solve for midtones m so that mtf(xp(peak)) = targetbg
112
99
  x = (peak - s) / max(h - s, 1e-8)
113
100
  x = float(np.clip(x, 1e-6, 1.0 - 1e-6))
@@ -136,14 +123,14 @@ def _mtf_params_unlinked(img_rgb01: np.ndarray,
136
123
  shadows_clipping: float = -2.8,
137
124
  targetbg: float = 0.25) -> dict:
138
125
  """
139
- Siril-style per-channel MTF parameter estimation, matching
126
+ per-channel MTF parameter estimation, matching
140
127
  find_unlinked_midtones_balance_default() / find_unlinked_midtones_balance().
141
128
 
142
129
  Works on float32 data assumed in [0,1].
143
130
  Returns dict with arrays: {'s': (C,), 'm': (C,), 'h': (C,)}.
144
131
  """
145
132
  """
146
- Siril-style per-channel MTF parameter estimation, matching
133
+ per-channel MTF parameter estimation, matching
147
134
  find_unlinked_midtones_balance_default() / find_unlinked_midtones_balance().
148
135
 
149
136
  Works on float32 data assumed in [0,1].
@@ -221,8 +208,6 @@ def _mtf_params_unlinked(img_rgb01: np.ndarray,
221
208
 
222
209
  def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
223
210
  """
224
- Scalar midtones transfer function matching the PixInsight / Siril spec.
225
-
226
211
  For x in [lo, hi], rescale to [0,1] and apply:
227
212
 
228
213
  M(x; m) = (m - 1) * xp / ((2*m - 1)*xp - m)
@@ -250,7 +235,7 @@ def _mtf_scalar(x: float, m: float, lo: float = 0.0, hi: float = 1.0) -> float:
250
235
  return 0.5
251
236
 
252
237
  y = num / den
253
- # clamp to [0,1] as PI/Siril do
238
+
254
239
  if y < 0.0:
255
240
  y = 0.0
256
241
  elif y > 1.0:
@@ -308,10 +293,8 @@ def _get_setting_any(settings, keys: tuple[str, ...], default: str = "") -> str:
308
293
 
309
294
  def starnet_starless_from_array(arr_rgb01: np.ndarray, settings, *, tmp_prefix="comet") -> np.ndarray:
310
295
  """
311
- Siril-style MTF round-trip for 32-bit data:
312
-
313
296
  1) Normalize to [0,1] (preserving overall scale separately)
314
- 2) Compute unlinked MTF params per channel (Siril auto-stretch)
297
+ 2) Compute unlinked MTF params per channel
315
298
  3) Apply unlinked MTF -> 16-bit TIFF for StarNet
316
299
  4) StarNet -> read starless 16-bit TIFF
317
300
  5) Apply per-channel MTF pseudoinverse with SAME params
@@ -351,7 +334,7 @@ def starnet_starless_from_array(arr_rgb01: np.ndarray, settings, *, tmp_prefix="
351
334
  xin = (x_in / scale_factor) if scale_factor > 1.0 else x_in
352
335
  xin = np.clip(xin, 0.0, 1.0)
353
336
 
354
- # --- Siril-style unlinked MTF params + pre-stretch ---
337
+
355
338
  mtf_params = _mtf_params_unlinked(xin, shadows_clipping=-2.8, targetbg=0.25)
356
339
  x_for_starnet = _apply_mtf_unlinked_rgb(xin, mtf_params).astype(np.float32, copy=False)
357
340
 
@@ -384,7 +367,7 @@ def starnet_starless_from_array(arr_rgb01: np.ndarray, settings, *, tmp_prefix="
384
367
  starless_s = np.repeat(starless_s, 3, axis=2)
385
368
  starless_s = np.clip(starless_s.astype(np.float32, copy=False), 0.0, 1.0)
386
369
 
387
- # --- Apply Siril-style pseudoinverse MTF with SAME params ---
370
+
388
371
  starless_lin01 = _invert_mtf_unlinked_rgb(starless_s, mtf_params)
389
372
 
390
373
  # Restore original scale if we normalized earlier
@@ -610,7 +593,7 @@ def _run_starnet(main, doc):
610
593
  input_image_path = os.path.join(starnet_dir, "imagetoremovestars.tif")
611
594
  output_image_path = os.path.join(starnet_dir, "starless.tif")
612
595
 
613
- # --- Prepare input for StarNet (Siril-style unlinked MTF pre-stretch for linear data) ---
596
+
614
597
  img_for_starnet = processing_norm
615
598
  if is_linear:
616
599
  mtf_params = _mtf_params_unlinked(processing_norm, shadows_clipping=-2.8, targetbg=0.25)
@@ -619,7 +602,7 @@ def _run_starnet(main, doc):
619
602
  # 🔐 Stash EXACT params for inverse step later
620
603
  try:
621
604
  setattr(main, "_starnet_stat_meta", {
622
- "scheme": "siril_mtf",
605
+ "scheme": "stretch_mtf",
623
606
  "s": np.asarray(mtf_params["s"], dtype=np.float32),
624
607
  "m": np.asarray(mtf_params["m"], dtype=np.float32),
625
608
  "h": np.asarray(mtf_params["h"], dtype=np.float32),
@@ -825,12 +808,12 @@ def _on_starnet_finished(main, doc, return_code, dialog, input_path, output_path
825
808
 
826
809
  # ---- Inversion back to the document’s domain ----
827
810
  if did_stretch:
828
- # Prefer the new Siril-style MTF meta if present
811
+
829
812
  meta = getattr(main, "_starnet_stat_meta", None)
830
813
  mtf_params_legacy = getattr(main, "_starnet_last_mtf_params", None)
831
814
 
832
- if isinstance(meta, dict) and meta.get("scheme") == "siril_mtf":
833
- dialog.append_text("Unstretching (Siril-style MTF pseudoinverse)...\n")
815
+ if isinstance(meta, dict) and meta.get("scheme") == "stretch_mtf":
816
+ dialog.append_text("Unstretching (MTF pseudoinverse)...\n")
834
817
  try:
835
818
  s_vec = np.asarray(meta.get("s"), dtype=np.float32)
836
819
  m_vec = np.asarray(meta.get("m"), dtype=np.float32)
@@ -845,7 +828,7 @@ def _on_starnet_finished(main, doc, return_code, dialog, input_path, output_path
845
828
 
846
829
  starless_rgb = np.clip(inv, 0.0, 1.0)
847
830
  except Exception as e:
848
- dialog.append_text(f"⚠️ Siril-style MTF inverse failed: {e}\n")
831
+ dialog.append_text(f"⚠️ MTF inverse failed: {e}\n")
849
832
 
850
833
  elif isinstance(meta, dict) and meta.get("scheme") == "statstretch":
851
834
  # Back-compat: statistical round-trip with bp/m0
@@ -302,6 +302,7 @@ class Icons:
302
302
 
303
303
  # Color
304
304
  SPCC = property(lambda self: _resource_path('spcc.png'))
305
+ MAGNITUDE = property(lambda self: _resource_path('magnitude.png'))
305
306
  DSE = property(lambda self: _resource_path('dse.png'))
306
307
  COLOR_WHEEL = property(lambda self: _resource_path('colorwheel.png'))
307
308
  SELECTIVE_COLOR = property(lambda self: _resource_path('selectivecolor.png'))
@@ -538,6 +539,7 @@ _LEGACY_ICON_MAP = {
538
539
  'script_icon_path': 'script.png',
539
540
  'planetprojection_path': '3dplanet.png',
540
541
  'finderchart_path': 'finderchart.png',
542
+ 'magnitude_path': 'magnitude.png',
541
543
  }
542
544
 
543
545
  _LEGACY_DATA_MAP = {
@@ -106,6 +106,26 @@ def _user_runtime_dir(status_cb=print) -> Path:
106
106
  _rt_dbg(f"_user_runtime_dir() -> {chosen}", status_cb)
107
107
  return chosen
108
108
 
109
+ def best_device(torch, *, prefer_cuda=True, prefer_dml=False, prefer_xpu=False):
110
+ if prefer_cuda and getattr(torch, "cuda", None) and torch.cuda.is_available():
111
+ return torch.device("cuda")
112
+
113
+ # DirectML (Windows)
114
+ if prefer_dml and platform.system() == "Windows":
115
+ try:
116
+ import torch_directml
117
+ d = torch_directml.device()
118
+ _ = (torch.ones(1, device=d) + 1).item()
119
+ return d
120
+ except Exception:
121
+ pass
122
+
123
+ # MPS
124
+ if getattr(getattr(torch, "backends", None), "mps", None) and torch.backends.mps.is_available():
125
+ return torch.device("mps")
126
+
127
+ return torch.device("cpu")
128
+
109
129
 
110
130
  # ──────────────────────────────────────────────────────────────────────────────
111
131
  # Shadowing & sanity checks
setiastro/saspro/sfcc.py CHANGED
@@ -528,7 +528,7 @@ class SFCCDialog(QDialog):
528
528
  self.run_spcc_btn.clicked.connect(self.run_spcc)
529
529
  row4.addWidget(self.run_spcc_btn)
530
530
 
531
- self.neutralize_chk = QCheckBox(self.tr("Background Neutralization")); self.neutralize_chk.setChecked(True); row4.addWidget(self.neutralize_chk)
531
+ self.neutralize_chk = QCheckBox(self.tr("Background Neutralization")); self.neutralize_chk.setChecked(False); row4.addWidget(self.neutralize_chk)
532
532
 
533
533
  self.run_grad_btn = QPushButton(self.tr("Run Gradient Extraction (Beta)"))
534
534
  f3 = self.run_grad_btn.font(); f3.setBold(True); self.run_grad_btn.setFont(f3)
@@ -822,65 +822,6 @@ class SFCCDialog(QDialog):
822
822
  idx = self.sens_combo.findText(current_s); self.sens_combo.setCurrentIndex(idx if idx != -1 else 0)
823
823
 
824
824
  # ── WCS utilities ──────────────────────────────────────────────────
825
- def initialize_wcs_from_header(self, header):
826
- if header is None:
827
- print("No FITS header available; cannot build WCS.")
828
- return
829
- try:
830
- hdr = header.copy()
831
-
832
- # --- normalize deprecated keywords ---
833
- if "RADECSYS" in hdr and "RADESYS" not in hdr:
834
- radesys_val = str(hdr["RADECSYS"]).strip()
835
- hdr["RADESYS"] = radesys_val
836
- try:
837
- del hdr["RADECSYS"]
838
- except Exception:
839
- pass
840
-
841
- alt_letters = {
842
- k[-1]
843
- for k in hdr.keys()
844
- if re.match(r"^CTYPE[12][A-Z]$", k)
845
- }
846
- for a in alt_letters:
847
- key = f"RADESYS{a}"
848
- if key not in hdr:
849
- hdr[key] = radesys_val
850
-
851
- if "EPOCH" in hdr and "EQUINOX" not in hdr:
852
- hdr["EQUINOX"] = hdr["EPOCH"]
853
- try:
854
- del hdr["EPOCH"]
855
- except Exception:
856
- pass
857
-
858
- # IMPORTANT: use the normalized hdr, not the original header
859
- self.wcs = WCS(hdr, naxis=2, relax=True)
860
-
861
- psm = self.wcs.pixel_scale_matrix
862
- self.pixscale = np.hypot(psm[0, 0], psm[1, 0]) * 3600.0
863
- self.center_ra, self.center_dec = self.wcs.wcs.crval
864
- self.wcs_header = self.wcs.to_header(relax=True)
865
-
866
- # Orientation from normalized header
867
- if "CROTA2" in hdr:
868
- try:
869
- self.orientation = float(hdr["CROTA2"])
870
- except Exception:
871
- self.orientation = None
872
- else:
873
- self.orientation = self.calculate_orientation(hdr)
874
-
875
- if self.orientation is not None:
876
- self.orientation_label.setText(f"Orientation: {self.orientation:.2f}°")
877
- else:
878
- self.orientation_label.setText("Orientation: N/A")
879
-
880
- except Exception as e:
881
- print("WCS initialization error:\n", e)
882
-
883
-
884
825
  def calculate_orientation(self, header):
885
826
  try:
886
827
  cd1_1 = float(header.get("CD1_1", 0.0))
@@ -1322,7 +1263,11 @@ class SFCCDialog(QDialog):
1322
1263
  # --- plot / UI feedback (unchanged) ---
1323
1264
  if getattr(self, "figure", None) is not None:
1324
1265
  self.figure.clf()
1325
-
1266
+ doc = self.doc_manager.get_active_document()
1267
+ if doc is not None:
1268
+ meta = dict(doc.metadata or {})
1269
+ meta["SFCC_star_list"] = list(self.star_list) # keep it JSON-ish
1270
+ self.doc_manager.update_active_document(doc.image, metadata=meta, step_name="SFCC Stars Cached", doc=doc)
1326
1271
  if templates_for_hist:
1327
1272
  uniq, cnt = np.unique(templates_for_hist, return_counts=True)
1328
1273
  types_str = ", ".join([str(u) for u in uniq])
@@ -1442,14 +1387,24 @@ class SFCCDialog(QDialog):
1442
1387
  dy = sources["y"] - star["y"]
1443
1388
  j = int(np.argmin(dx * dx + dy * dy))
1444
1389
  if (dx[j] * dx[j] + dy[j] * dy[j]) < (3.0 ** 2):
1445
- xi, yi = int(round(float(sources["x"][j]))), int(round(float(sources["y"][j])))
1446
- if 0 <= xi < W and 0 <= yi < H:
1447
- raw_matches.append({
1448
- "sim_index": i,
1449
- "template": star.get("pickles_match") or star["sp_clean"],
1450
- "x_pix": xi,
1451
- "y_pix": yi
1452
- })
1390
+ x_c = float(sources["x"][j])
1391
+ y_c = float(sources["y"][j])
1392
+
1393
+ raw_matches.append({
1394
+ "sim_index": i,
1395
+ "template": star.get("pickles_match") or star["sp_clean"],
1396
+ "src_index": j,
1397
+
1398
+ # New canonical centroid keys
1399
+ "x": x_c,
1400
+ "y": y_c,
1401
+
1402
+ # Back-compat keys used elsewhere (gradient step, older code paths)
1403
+ "x_pix": x_c,
1404
+ "y_pix": y_c,
1405
+
1406
+ "a": float(sources["a"][j]),
1407
+ })
1453
1408
 
1454
1409
  if not raw_matches:
1455
1410
  QMessageBox.warning(self, "No Matches", "No SIMBAD star matched to SEP detections.")
@@ -1525,14 +1480,65 @@ class SFCCDialog(QDialog):
1525
1480
  except Exception as e:
1526
1481
  print(f"[SFCC] Warning: failed to load/integrate template {pname}: {e}")
1527
1482
 
1483
+ def measure_star_rgb_aperture(img_rgb_f32: np.ndarray, x: float, y: float, r: float,
1484
+ rin: float, rout: float) -> tuple[float, float, float]:
1485
+ # SEP expects float32, C-contiguous, and (x,y) in pixel coords
1486
+ R = np.ascontiguousarray(img_rgb_f32[..., 0], dtype=np.float32)
1487
+ G = np.ascontiguousarray(img_rgb_f32[..., 1], dtype=np.float32)
1488
+ B = np.ascontiguousarray(img_rgb_f32[..., 2], dtype=np.float32)
1489
+
1490
+ # sum_circle returns (flux, fluxerr, flag) when err not provided; handle either form
1491
+ def _sum(ch):
1492
+ out = sep.sum_circle(ch, np.array([x]), np.array([y]), r,
1493
+ subpix=5, bkgann=(rin, rout))
1494
+ # Depending on sep version, out can be (flux, fluxerr, flag) or (flux, flag)
1495
+ if len(out) == 3:
1496
+ flux, _fluxerr, flag = out
1497
+ else:
1498
+ flux, flag = out
1499
+ return float(flux[0]), int(flag[0])
1500
+
1501
+ fR, flR = _sum(R)
1502
+ fG, flG = _sum(G)
1503
+ fB, flB = _sum(B)
1504
+
1505
+ # If any flags set, you can reject (edge, etc.)
1506
+ if (flR | flG | flB) != 0:
1507
+ return None, None, None
1508
+
1509
+ return fR, fG, fB
1510
+
1511
+
1528
1512
  # ---- Main match loop (measure from 'base' only) ----
1529
1513
  for m in raw_matches:
1530
- xi, yi, sp = m["x_pix"], m["y_pix"], m["template"]
1514
+ xi = float(m.get("x_pix", m["x"]))
1515
+ yi = float(m.get("y_pix", m["y"]))
1516
+ sp = m["template"]
1531
1517
 
1532
1518
  # measure on the SEP working copy (already BN’d, only one pedestal handling)
1533
- Rm = float(base[yi, xi, 0])
1534
- Gm = float(base[yi, xi, 1])
1535
- Bm = float(base[yi, xi, 2])
1519
+ x = float(m["x"])
1520
+ y = float(m["y"])
1521
+
1522
+ # Aperture radius choice (simple + robust)
1523
+ # sources["a"] is roughly semi-major sigma-ish from SEP; a common quick rule:
1524
+ # r ~ 2.5 * a, with sane clamps.
1525
+ a = float(m.get("a", 1.5))
1526
+ r = float(np.clip(2.5 * a, 2.0, 12.0))
1527
+
1528
+ # Annulus (your “kicker”): inner/outer in pixels
1529
+ rin = float(np.clip(3.0 * r, 6.0, 40.0))
1530
+ rout = float(np.clip(5.0 * r, rin + 2.0, 60.0))
1531
+
1532
+ meas = measure_star_rgb_aperture(base, x, y, r, rin, rout)
1533
+ if meas[0] is None:
1534
+ continue
1535
+ Rm, Gm, Bm = meas
1536
+
1537
+ if Gm <= 0:
1538
+ continue
1539
+ meas_RG = Rm / Gm
1540
+ meas_BG = Bm / Gm
1541
+
1536
1542
  if Gm <= 0:
1537
1543
  continue
1538
1544
 
@@ -1656,7 +1662,8 @@ class SFCCDialog(QDialog):
1656
1662
  QApplication.processEvents()
1657
1663
 
1658
1664
  eps = 1e-8
1659
- calibrated = base.copy()
1665
+ #calibrated = base.copy()
1666
+ calibrated = img_float.copy()
1660
1667
 
1661
1668
  R = calibrated[..., 0]
1662
1669
  G = calibrated[..., 1]
@@ -5,7 +5,7 @@ import numpy as np
5
5
 
6
6
  # Always route through our runtime shim so ALL GPU users share the same backend.
7
7
  # Nothing heavy happens at import; we only resolve Torch when needed.
8
- from .runtime_torch import import_torch, add_runtime_to_sys_path
8
+ from .runtime_torch import import_torch, add_runtime_to_sys_path, best_device
9
9
 
10
10
  # Algorithms supported by the GPU path here (names match your UI/CPU counterparts)
11
11
  _SUPPORTED = {
@@ -31,47 +31,37 @@ _SUPPORTED = {
31
31
  _TORCH = None
32
32
  _DEVICE = None
33
33
 
34
- def _get_torch(prefer_cuda: bool = True):
35
- """
36
- Resolve and cache the torch module via the SAS runtime shim.
37
- This may install/repair torch into the per-user runtime if needed.
38
- """
34
+
35
+
36
+ def _get_torch(prefer_cuda: bool = True, prefer_dml: bool = False):
39
37
  global _TORCH, _DEVICE
40
38
  if _TORCH is not None:
41
39
  return _TORCH
42
40
 
43
- # In frozen builds, help the process see the runtime site-packages first.
44
41
  try:
45
42
  add_runtime_to_sys_path(lambda *_: None)
46
43
  except Exception:
47
44
  pass
48
45
 
49
- # Import (and if necessary, install) torch using the unified runtime.
50
- torch = import_torch(prefer_cuda=prefer_cuda, status_cb=lambda *_: None)
46
+ # Let runtime_torch install the right stack
47
+ torch = import_torch(prefer_cuda=prefer_cuda, prefer_dml=prefer_dml, status_cb=lambda *_: None)
48
+ _DEVICE = best_device(torch, prefer_cuda=True, prefer_dml=prefer_dml)
51
49
  _TORCH = torch
52
50
  _force_fp32_policy(torch)
53
51
 
54
- # Choose the best device once; cheap calls, but cached anyway
55
- try:
56
- if hasattr(torch, "cuda") and torch.cuda.is_available():
57
- _DEVICE = torch.device("cuda")
58
- elif getattr(getattr(torch, "backends", None), "mps", None) and torch.backends.mps.is_available():
59
- _DEVICE = torch.device("mps")
60
- else:
61
- # Try DirectML for AMD/Intel GPUs on Windows
62
- try:
63
- import torch_directml
64
- dml_device = torch_directml.device()
65
- # Quick sanity check
66
- _ = (torch.ones(1, device=dml_device) + 1).item()
67
- _DEVICE = dml_device
68
- except Exception:
69
- _DEVICE = torch.device("cpu")
70
- except Exception:
52
+ # Device selection: CUDA first, else CPU.
53
+ # (If runtime_torch installed DML, then your app should choose DML device elsewhere
54
+ # OR you add a runtime_torch helper to return it.)
55
+ if hasattr(torch, "cuda") and torch.cuda.is_available():
56
+ _DEVICE = torch.device("cuda")
57
+ elif getattr(getattr(torch, "backends", None), "mps", None) and torch.backends.mps.is_available():
58
+ _DEVICE = torch.device("mps")
59
+ else:
71
60
  _DEVICE = torch.device("cpu")
72
61
 
73
62
  return _TORCH
74
63
 
64
+
75
65
  def _device():
76
66
  if _DEVICE is not None:
77
67
  return _DEVICE
@@ -246,11 +236,52 @@ def torch_reduce_tile(
246
236
  ts = torch.from_numpy(ts_np).to(dev, dtype=torch.float32, non_blocking=True)
247
237
 
248
238
  # Weights broadcast to 4D
239
+ # Weights -> broadcastable 4D tensor (F,1,1,1) or (F,1,1,C) or (F,H,W,1) or (F,H,W,C)
249
240
  weights_np = np.asarray(weights_np, dtype=np.float32)
241
+
242
+ if weights_np.ndim == 0:
243
+ # scalar -> per-frame scalar (rare)
244
+ weights_np = np.full((F,), float(weights_np), dtype=np.float32)
245
+
250
246
  if weights_np.ndim == 1:
251
- w = torch.from_numpy(weights_np).to(dev, dtype=torch.float32, non_blocking=True).view(F,1,1,1)
247
+ if weights_np.shape[0] != F:
248
+ raise ValueError(f"weights shape {weights_np.shape} does not match F={F}")
249
+ w_np = weights_np.reshape(F, 1, 1, 1)
250
+
251
+ elif weights_np.ndim == 2:
252
+ # Most important fix: (F,C) per-channel weights
253
+ if weights_np.shape == (F, C):
254
+ w_np = weights_np.reshape(F, 1, 1, C)
255
+ # Sometimes people accidentally pass (F,H) or (F,W) -> reject loudly
256
+ else:
257
+ raise ValueError(
258
+ f"Unsupported 2D weights shape {weights_np.shape}. "
259
+ f"Expected (F,C)=({F},{C}) for per-channel weights."
260
+ )
261
+
262
+ elif weights_np.ndim == 3:
263
+ # (F,H,W) -> treat as single-channel weight map
264
+ if weights_np.shape == (F, H, W):
265
+ w_np = weights_np[..., None] # (F,H,W,1)
266
+ else:
267
+ raise ValueError(
268
+ f"Unsupported 3D weights shape {weights_np.shape}. Expected (F,H,W)=({F},{H},{W})."
269
+ )
270
+
271
+ elif weights_np.ndim == 4:
272
+ if weights_np.shape == (F, H, W, 1) or weights_np.shape == (F, H, W, C):
273
+ w_np = weights_np
274
+ else:
275
+ raise ValueError(
276
+ f"Unsupported 4D weights shape {weights_np.shape}. "
277
+ f"Expected (F,H,W,1) or (F,H,W,C)=({F},{H},{W},{C})."
278
+ )
252
279
  else:
253
- w = torch.from_numpy(weights_np).to(dev, dtype=torch.float32, non_blocking=True)
280
+ raise ValueError(f"Unsupported weights ndim={weights_np.ndim} shape={weights_np.shape}")
281
+
282
+ # Host -> device
283
+ w = torch.from_numpy(w_np).to(dev, dtype=torch.float32, non_blocking=False)
284
+
254
285
 
255
286
  algo = algo_name
256
287
  valid = torch.isfinite(ts)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: setiastrosuitepro
3
- Version: 1.8.2
3
+ Version: 1.8.3
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
@@ -88,6 +88,7 @@ setiastro/images/linearfit.png,sha256=F6mP2n2JT97l-bMPEGE-ZTVuSjhMTjdRCnlBqKU3o-
88
88
  setiastro/images/linearfit.svg,sha256=0uJlc6Coa2infF84KRg0fVh6dUKzxPRXZRtNJ_tqW8o,1272
89
89
  setiastro/images/LInsert.png,sha256=WKw54NDrwqwo2NiofmasEkdQQB-f9QJoEL1aLafNFRY,7746
90
90
  setiastro/images/livestacking.png,sha256=zNBAsYHoMArTpHk21hNvTWpstv4YCFg2-_sJBS5a8U8,620062
91
+ setiastro/images/magnitude.png,sha256=_DZ8SpksNmGR5aeHdDG-SwrXvrrj602-gRy7b_BnDQ0,185695
91
92
  setiastro/images/mask.png,sha256=yXPcEJ7l7MXr5ejhBCxQkvIHiZUowRcPkOqozdJjJVo,64164
92
93
  setiastro/images/maskapply.png,sha256=p6beAt3EQSiS3LCTPCNEh30rJll6Gw1mj5mHjG7Mpc4,28120
93
94
  setiastro/images/maskcreate.png,sha256=5bbA2Ydo7V-f7e89-Squ3XTk_ehsQVBpf87rOapmh7c,30288
@@ -184,7 +185,7 @@ setiastro/qml/ResourceMonitor.qml,sha256=k9_qXKAZLi8vj-5BffJTJu_UkRnxunZKn53Hthd
184
185
  setiastro/saspro/__init__.py,sha256=6o8orhytzBWyJWBdG37xPcT6IVPZOG8d22FBVzn0Kac,902
185
186
  setiastro/saspro/__main__.py,sha256=eF2aX3QNxdniVgj1CYt5VPowRx0E-5mPn5yGRFgYIBs,40815
186
187
  setiastro/saspro/_generated/__init__.py,sha256=HbruQfKNbbVL4kh_t4oVG3UeUieaW8MUaqIcDCmnTvA,197
187
- setiastro/saspro/_generated/build_info.py,sha256=BIM9yWgNJRpUTopkx2W0P8gCEaFSdinSdmWInMEV3Z4,111
188
+ setiastro/saspro/_generated/build_info.py,sha256=czb2W9pUmwy0CCVz9X5fVPaEy73PRGnOEQTXNXAgF1w,111
188
189
  setiastro/saspro/abe.py,sha256=Yj3cmwRZoX0XyiyFWuiARZuF-O9lWnOpplWQhykz0Ms,59777
189
190
  setiastro/saspro/abe_preset.py,sha256=u9t16yTb9v98tLjhvh496Fsp3Z-dNiBSs5itnAaJwh8,7750
190
191
  setiastro/saspro/aberration_ai.py,sha256=8LtDu_KuqvL-W0MTUIJq9KguMjJPiUEQjMUibY16H-4,41673
@@ -197,7 +198,7 @@ setiastro/saspro/astrobin_exporter.py,sha256=-MZFoUVVZ3FItTbwoqoLzuOsrR3R5RQ7zf4
197
198
  setiastro/saspro/astrospike.py,sha256=Or-14MS3iZrn3q1teNf-BkjVdMCsZYaON7T95dm96oI,5930
198
199
  setiastro/saspro/astrospike_python.py,sha256=Ytl9EWsRDQBXZtwMqtCBjVdiEEr9isUeLnGmcuEw1fQ,74932
199
200
  setiastro/saspro/autostretch.py,sha256=mSDXjbKjip7BrB19QZhniBxlsf8C_X49IVlc8-0bN68,8298
200
- setiastro/saspro/backgroundneutral.py,sha256=MFlBEwR65ASNXIY0S-0ZUaS89FCmNV_qafPs-OxUlO4,26383
201
+ setiastro/saspro/backgroundneutral.py,sha256=P4BcIXPM1GyRlPfSvdKiy_knHtNJbcOq9CGcOI8bXWs,27855
201
202
  setiastro/saspro/batch_convert.py,sha256=O46KyB-DBZiTHokaWt_op1GDRr3juSaDSeFzP3pv1Zs,12377
202
203
  setiastro/saspro/batch_renamer.py,sha256=PZe2MEwjg0n055UETIbwnwdfAux4heTVx5gr3qnNW5g,21900
203
204
  setiastro/saspro/blemish_blaster.py,sha256=aiIpr-qpDmfaLZErUGUtwQgSTnaBHz3l14JvDWRfL3w,22983
@@ -235,7 +236,7 @@ setiastro/saspro/dnd_mime.py,sha256=QNG97JQJc3xNQ5cPD4zwbC7OZTf9BzqjXE2SvZV9H5Q,
235
236
  setiastro/saspro/doc_manager.py,sha256=mNY6hTcQuGbHvuusRxJ_GJe3VhLrp053WZbfuNNUx28,113407
236
237
  setiastro/saspro/exoplanet_detector.py,sha256=2o5SV8VB3BDZudUTKg62u5OHk2L6Ohk2Hekci_JESR0,97222
237
238
  setiastro/saspro/file_utils.py,sha256=zB2BqVnDVLbg_5eO3PIFgYu_Ulm5M3MXQUuzNdvO6Q4,7569
238
- setiastro/saspro/finder_chart.py,sha256=TNSzbBn4TfSQLb3t13ircWlLJVmM9x2EmW6avNGieYA,57781
239
+ setiastro/saspro/finder_chart.py,sha256=DwUqXRZY5Jm-JZ6TRqMS36lLKUUu4DQ-FCpNLK2vzRU,58144
239
240
  setiastro/saspro/fitsmodifier.py,sha256=jYrSUwMacohP8ErFdJy0sr8Ld4YrTUgDdwaDr-_pRWg,30362
240
241
  setiastro/saspro/fix_bom.py,sha256=87NLdfL-eHLctZ8Yz6sTtchwXiX4GPJ133OQrNzhLSU,1121
241
242
  setiastro/saspro/free_torch_memory.py,sha256=mbyB16clbPm71wCdhtJ6PjOkGuQ_c0GS6OopYdPhlLc,1461
@@ -247,16 +248,16 @@ setiastro/saspro/ghs_preset.py,sha256=Zw3MJH5rEz7nqLdlmRBm3vYXgyopoupyDGAhM-PRXq
247
248
  setiastro/saspro/graxpert.py,sha256=XSLeBhlAY2DkYUw93j2OEOuPLOPfzWYcT5Dz3JhhpW8,22579
248
249
  setiastro/saspro/graxpert_preset.py,sha256=ESds2NPMPfsBHWNBfyYZ1rFpQxZ9gPOtxwz8enHfFfc,10719
249
250
  setiastro/saspro/gui/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
250
- setiastro/saspro/gui/main_window.py,sha256=KHabN-GKjbApDQqMzm3j9i256DfagOJvaWYea2uJ4mQ,377079
251
+ setiastro/saspro/gui/main_window.py,sha256=RpVjXo144T9oAkEtC3G1zXPlHEoM1hqaNxt8MSnoK6Y,380875
251
252
  setiastro/saspro/gui/mixins/__init__.py,sha256=ubdTIri0xoRs6MwDnEaVsAHbMxuPqz0CZcYcue3Mkcc,836
252
253
  setiastro/saspro/gui/mixins/dock_mixin.py,sha256=C6IWKM-uP2ekqO7ozVQQcTnEgAkUL_OaRZbncIdrh8g,23893
253
254
  setiastro/saspro/gui/mixins/file_mixin.py,sha256=p6gKJrDTKovK4YzowDQy1Z5nNsspQe7B9ICRXvCUvxc,17893
254
255
  setiastro/saspro/gui/mixins/geometry_mixin.py,sha256=x-HyLXGFhEs8SJuXT8EF6tS3XJaH8IhAP-ZFJ2BS2Lw,19038
255
- setiastro/saspro/gui/mixins/header_mixin.py,sha256=kfZUJviB61c8NBXFB3MVBEcRPX_36ZV8tUZNJkDmMJ0,17253
256
+ setiastro/saspro/gui/mixins/header_mixin.py,sha256=kFYBVBpMUtZzfs2ybRZ8TOXjLaDS_oPmYE6XRqvyrDU,17752
256
257
  setiastro/saspro/gui/mixins/mask_mixin.py,sha256=hrC5jLWxUZgQiYUXp7nvECo2uiarK7_HKgawG4HGB9w,15711
257
- setiastro/saspro/gui/mixins/menu_mixin.py,sha256=9zcTSDFVWXJdsrVfNAcz6GvPJCRuKkuToJixkYb1GJo,16960
258
+ setiastro/saspro/gui/mixins/menu_mixin.py,sha256=MWDWdOBolPce5Y9NDKd3OuZ7fp0CxQKdZvlOvXeci7g,17007
258
259
  setiastro/saspro/gui/mixins/theme_mixin.py,sha256=td6hYnZn17lXlFxQOXgK7-qfKo6CIJACF8_zrULlEkc,20740
259
- setiastro/saspro/gui/mixins/toolbar_mixin.py,sha256=V34uxLucNEm3SDbLndRNlNDPMBWGYm_OAL6GwlX7ZkU,93287
260
+ setiastro/saspro/gui/mixins/toolbar_mixin.py,sha256=8XBVkXif7hyCFmduu12rUEG7pB75sQ_b5xIqgI-N280,93757
260
261
  setiastro/saspro/gui/mixins/update_mixin.py,sha256=rq7J0Pcn_gO8UehCGVtcSvf-MDbmeGf796h0JBGr0sM,16545
261
262
  setiastro/saspro/gui/mixins/view_mixin.py,sha256=EacXxtoAvkectGgLiahf3rHv2AaqKDQUteocVV_P834,17850
262
263
  setiastro/saspro/gui/statistics_dialog.py,sha256=celhcsHcp3lNpox_X0ByAiYE80qFaF-dYyP8StzgrcU,1955
@@ -274,7 +275,7 @@ setiastro/saspro/imageops/narrowband_normalization.py,sha256=q1KdHS7bjRYW-N0Jbsz
274
275
  setiastro/saspro/imageops/scnr.py,sha256=jLHxBU4KQ9Mu61Cym5YUq0pwxajJmGrPQRV2pug9LXE,1330
275
276
  setiastro/saspro/imageops/serloader.py,sha256=uxZUobb3ebvd5xWr4XorOSNmtg7_9EMj6WqQ-E1fnxo,49028
276
277
  setiastro/saspro/imageops/starbasedwhitebalance.py,sha256=urssLdgKD7J_LOuscjbf9zFImnUWoH-sotGvtbxfxTo,6589
277
- setiastro/saspro/imageops/stretch.py,sha256=E_Ydh5VxUHrehwivhvVDvF8y89U6SRyjIFUQgJjHWXI,27113
278
+ setiastro/saspro/imageops/stretch.py,sha256=uAJ5LY5zRdaQiwqIcL1AVfZn8xy9vaLeUKgrcgJUYj0,27134
278
279
  setiastro/saspro/isophote.py,sha256=eSzlyBN_tZJSJTKlnBjwHY0zdw0SXPV6BRk_jt9TlVU,53753
279
280
  setiastro/saspro/layers.py,sha256=X83XlwJhH0zjjC6b3CGeQg4dBp4w9zxBGfZVwtY1h3Q,12997
280
281
  setiastro/saspro/layers_dock.py,sha256=1vdxAPmdQ1_8ZqA4IySspqvcydB_alSj3AARoWbb29Y,42735
@@ -288,6 +289,7 @@ setiastro/saspro/live_stacking.py,sha256=txqnqzPCJa4Ub-XQmfHRy8xuqY-5L129ryuY2Lh
288
289
  setiastro/saspro/log_bus.py,sha256=1H6cTrv138I89BU5K7rhA-QRXPxY0_2C5eegS38Mc-o,178
289
290
  setiastro/saspro/logging_config.py,sha256=vRETX9Nw4bYaBDZAbglo1m7V-2rh85QGE8eRS-yoOcA,14868
290
291
  setiastro/saspro/luminancerecombine.py,sha256=Y2l96xHPLj1K7W7TptvYZ8d4WagsqQ3uM40jokeHVkI,19264
292
+ setiastro/saspro/magnitude_tool.py,sha256=LDtkqh3wsva9ojunyXEyjH3AOGxRGgYUosf7Cononc8,67516
291
293
  setiastro/saspro/main_helpers.py,sha256=PD8pvO3-pgeWWtyRcTUVdrzp2h-Hui-iKrYJ7Z0Y3_Q,7003
292
294
  setiastro/saspro/mask_creation.py,sha256=aVa3Ie58dqsN945pZOQcyBYxuKj_03Fs9oF8ilWdYAk,44651
293
295
  setiastro/saspro/masks_core.py,sha256=2JcQeOFWzCnVxqNYBp4sHeRLn_b5LCpbZaAy6eNH1I8,1852
@@ -327,14 +329,14 @@ setiastro/saspro/psf_utils.py,sha256=K3R4BjUzsopLRuLTbPlm8lBw3-x9twNWG4bK-4phDM4
327
329
  setiastro/saspro/psf_viewer.py,sha256=gaQhzLIhk0i5aRXREzWeieaD5VY22wOJx-ehbfaETuc,22952
328
330
  setiastro/saspro/pyi_rthook_astroquery.py,sha256=Omt8U0gMI3efw3y-GUUJtBYUylwT1OMn0285QW3kRzE,3993
329
331
  setiastro/saspro/remove_green.py,sha256=bd_4xLe-tEYPncOSVUHh-fVIrPWG6K0ESKgAygrwj_M,12082
330
- setiastro/saspro/remove_stars.py,sha256=PxOBJLsztODBRjbqM1HqQzIT9z7YovsEOfIo8netboU,58424
332
+ setiastro/saspro/remove_stars.py,sha256=N-vAeoha5q6kT-TKUrzUSPx3naWFFFUtehZTJ8t4VWA,57339
331
333
  setiastro/saspro/remove_stars_preset.py,sha256=HcR50bRXqiJSx4jMyle389g8b6SIulXHxgSysk07vdk,19195
332
- setiastro/saspro/resources.py,sha256=Xe6PDEuO12THx0sMPD1Mi4pFiAmaVlYtCQRJpStykxA,29533
334
+ setiastro/saspro/resources.py,sha256=ShZhMCY_AugPTs7dFxuEfjn329Uxg2KcPIZamPskB8w,29645
333
335
  setiastro/saspro/rgb_combination.py,sha256=8MKlxsbn5Pmiq3Vh28fMVYgYG4EIsvOYrwSJisJGyds,8765
334
336
  setiastro/saspro/rgb_extract.py,sha256=FZRA2qac8LR2u-jNcQP-1xFeiOYVBo06-Q3Xl0KAlZM,624
335
337
  setiastro/saspro/rgbalign.py,sha256=XwBDj5t_zj9Uh-PaX1SNlzix9PftnQBIoTMtRPvYK80,46528
336
338
  setiastro/saspro/runtime_imports.py,sha256=tgIHH10cdAEbCTOmcs7retQhAww2dEc_3mKrv_m8W9s,206
337
- setiastro/saspro/runtime_torch.py,sha256=VUPvyYxYfZpkgSaDtd_6PjQZ6FT0aiPB-I40-A5G3qc,53198
339
+ setiastro/saspro/runtime_torch.py,sha256=efRUsp6NUpg2eefyJFNj_gk1ul5rZmZkQ3A_2aXtk9E,53882
338
340
  setiastro/saspro/save_options.py,sha256=wUvuZBCDcQFDXF846njBd_4xoSRa2Ih1-I8Hf5ZTLbk,3648
339
341
  setiastro/saspro/selective_color.py,sha256=hx-9X4jeso797ifoBmwp9hNagPW4cvNPs9-T0JLHce0,64804
340
342
  setiastro/saspro/ser_stack_config.py,sha256=OMQHnRysXMW2l05PGLlwu1JxEBAcdUpBOEnFvn9jXP0,4921
@@ -342,7 +344,7 @@ setiastro/saspro/ser_stacker.py,sha256=HLJgX-Dc8hIajNupoK6U-APbr1vsHOycerL5KeYDI
342
344
  setiastro/saspro/ser_stacker_dialog.py,sha256=TfQBwbEF7722jJAb4nML-eQPe247usbeaby98sz_Hho,69471
343
345
  setiastro/saspro/ser_tracking.py,sha256=HU5F2ZAekjBsKu-nYQVqbx3FukUqGYTkTK6J9n0tUgg,8077
344
346
  setiastro/saspro/serviewer.py,sha256=QvPtJky2IzrywXaOYjeSZSNY0I64TSrzfgH7vRgGk7M,68763
345
- setiastro/saspro/sfcc.py,sha256=7fffs8x8d5hXK7MPaqtaWNrujk2lB3WVvQSKXh_zaY0,89221
347
+ setiastro/saspro/sfcc.py,sha256=YrYgy2_UXYIOHqQmjZyZE2mVu8xD-Xh7QYFBEk5D-pM,89736
346
348
  setiastro/saspro/shortcuts.py,sha256=QvFBXN_S8jqEwaP9m4pJMLVqzBmxo5HrjWhVCV9etQg,138256
347
349
  setiastro/saspro/signature_insert.py,sha256=2bIAayZfprVtutq4iQDf6xhPSpW52lJTRP5IEHrHZY0,70851
348
350
  setiastro/saspro/stacking_suite.py,sha256=ZALG0IMIfNkHnX5GMgtjQh00CYAecNTmWMOeDTcQZLc,896032
@@ -358,7 +360,7 @@ setiastro/saspro/supernovaasteroidhunter.py,sha256=PgCq3IR2pzsNBx6IxdhqfZlKPMZFh
358
360
  setiastro/saspro/swap_manager.py,sha256=7hJvQsnm1LSqLmXrj5uQkTSqNJq1SvrtrkCQPJdNego,4643
359
361
  setiastro/saspro/texture_clarity.py,sha256=WduV8AyZPFBSdV2rfBJAICpfHu2o_ZCDtI8p2CcfNOs,22928
360
362
  setiastro/saspro/torch_backend.py,sha256=e8ZNIeSP33NjNRH4yNhrhZRo5k7tNEpJ6FNlOQ-kp8I,2473
361
- setiastro/saspro/torch_rejection.py,sha256=p5xpslY4iBvqGZdlq-QObOssVx3maAG4CQHQxXETq64,19426
363
+ setiastro/saspro/torch_rejection.py,sha256=wXbnFoVo6pmq-bJFwi7Kr8k3dftmu4HDcR8olElyqDo,20579
362
364
  setiastro/saspro/translations/all_source_strings.json,sha256=bIcZr4TnYpPi4wJGG0eU0VLCMBiS4ZRzTIHLOULqtRY,136852
363
365
  setiastro/saspro/translations/ar_translations.py,sha256=gjsHCj_dcw6suhTDWzq0G_I2Gp8jgNrjYejysO3fIqc,294881
364
366
  setiastro/saspro/translations/de_translations.py,sha256=-ewyI-wo-IjaLtQujsJmTkB9qgE_UnAyuWVCjGSMhus,241427
@@ -421,9 +423,9 @@ setiastro/saspro/wimi.py,sha256=UARHSG6vCEqdNMxDX545Bdg12D_KzBI-2Abd9mz92-I,3146
421
423
  setiastro/saspro/wims.py,sha256=HDfVI3Ckf5OJEJLH8NI36pFc2USZnETpb4UDIvweNX4,27450
422
424
  setiastro/saspro/window_shelf.py,sha256=hpId8uRPq7-UZ3dWW5YzH_v4TchQokGFoPGM8dyaIZA,9448
423
425
  setiastro/saspro/xisf.py,sha256=n7qUjs2zOBqUtdc8AcCm-alLlz2LsZ5SIAQbvOkD_6o,52696
424
- setiastrosuitepro-1.8.2.dist-info/entry_points.txt,sha256=g7cHWhUSiIP7mkyByG9JXGWWlHKeVC2vL7zvB9U-vEU,236
425
- setiastrosuitepro-1.8.2.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
426
- setiastrosuitepro-1.8.2.dist-info/licenses/license.txt,sha256=KCwYZ9VpVwmzjelDq1BzzWqpBvt9nbbapa-woz47hfQ,123930
427
- setiastrosuitepro-1.8.2.dist-info/METADATA,sha256=D4v4qc2jXq8mAFQgjShl7gZHy0YXyi7oXo7-m3JR08Q,9996
428
- setiastrosuitepro-1.8.2.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
429
- setiastrosuitepro-1.8.2.dist-info/RECORD,,
426
+ setiastrosuitepro-1.8.3.dist-info/entry_points.txt,sha256=g7cHWhUSiIP7mkyByG9JXGWWlHKeVC2vL7zvB9U-vEU,236
427
+ setiastrosuitepro-1.8.3.dist-info/licenses/LICENSE,sha256=IwGE9guuL-ryRPEKi6wFPI_zOhg7zDZbTYuHbSt_SAk,35823
428
+ setiastrosuitepro-1.8.3.dist-info/licenses/license.txt,sha256=KCwYZ9VpVwmzjelDq1BzzWqpBvt9nbbapa-woz47hfQ,123930
429
+ setiastrosuitepro-1.8.3.dist-info/METADATA,sha256=CoOXqoANkb55qklzYM_o0bp609k043ATzcU2O4uQUNY,9996
430
+ setiastrosuitepro-1.8.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
431
+ setiastrosuitepro-1.8.3.dist-info/RECORD,,