noisechisel-torch 0.1.0__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.
@@ -0,0 +1,25 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """PyTorch re-implementation of Gnuastro's NoiseChisel (2D images)."""
3
+ from .core import (
4
+ DEFAULT_KERNEL,
5
+ NoiseChisel,
6
+ NoiseChiselParams,
7
+ NoiseChiselResult,
8
+ gaussian_kernel,
9
+ noisechisel,
10
+ )
11
+ from .io import run_fits, write_fits
12
+
13
+ __version__ = "0.1.0"
14
+
15
+ __all__ = [
16
+ "DEFAULT_KERNEL",
17
+ "NoiseChisel",
18
+ "NoiseChiselParams",
19
+ "NoiseChiselResult",
20
+ "gaussian_kernel",
21
+ "noisechisel",
22
+ "run_fits",
23
+ "write_fits",
24
+ "__version__",
25
+ ]
@@ -0,0 +1,123 @@
1
+ # SPDX-License-Identifier: GPL-3.0-or-later
2
+ """Command line interface, modelled on ``astnoisechisel``::
3
+
4
+ noisechisel-torch image.fits -h 1 -o out.fits --tilesize 30,30 --device cuda
5
+ """
6
+ from __future__ import annotations
7
+
8
+ import argparse
9
+ import os
10
+ import sys
11
+ import time
12
+ from dataclasses import fields
13
+ from typing import get_args, get_origin, get_type_hints
14
+
15
+ from .core import NoiseChiselParams
16
+
17
+ # Sizes given in FITS (x, y) order on the command line, as in Gnuastro; the
18
+ # Python API uses numpy (rows, cols) order.
19
+ FITS_ORDER = ("tilesize", "largetilesize")
20
+
21
+ # Gnuastro's short option names.
22
+ SHORT = {"meanmedqdiff": "-Q", "qthresh": "-t", "erode": "-e", "opening": "-p",
23
+ "minskyfrac": "-B", "sigmaclip": "-s", "dthresh": "-R", "snminarea": "-m",
24
+ "snquant": "-c", "detgrowquant": "-d"}
25
+
26
+
27
+ def _parse_value(text, typ):
28
+ origin = get_origin(typ)
29
+ args = [a for a in get_args(typ) if a is not type(None)]
30
+ if origin is not None and type(None) in get_args(typ): # Optional[...]
31
+ if text.lower() == "none":
32
+ return None
33
+ return _parse_value(text, args[0])
34
+ if origin is tuple:
35
+ parts = text.split(",")
36
+ return tuple(a(p) for a, p in zip(args, parts))
37
+ if typ is bool:
38
+ return text.lower() in ("1", "true", "yes", "y")
39
+ return typ(text)
40
+
41
+
42
+ def _hdu(text):
43
+ return int(text) if text.lstrip("-").isdigit() else text
44
+
45
+
46
+ def _read_kernel(path, hdu):
47
+ from astropy.io import fits
48
+ return fits.getdata(path, ext=_hdu(hdu))
49
+
50
+
51
+ def build_parser():
52
+ ap = argparse.ArgumentParser(
53
+ prog="noisechisel-torch", add_help=False,
54
+ description="PyTorch NoiseChisel: detect signal and measure Sky/STD of a FITS image.")
55
+ ap.add_argument("--help", action="help")
56
+ ap.add_argument("input", help="input FITS image")
57
+ ap.add_argument("-h", "--hdu", default="0", help="input HDU (number or name)")
58
+ ap.add_argument("-o", "--output", help="output FITS (default: <input>_detected.fits)")
59
+ ap.add_argument("--device", default=None, help="cpu | cuda | cuda:N (default: auto)")
60
+ ap.add_argument("-N", "--numthreads", type=int, default=None, metavar="N",
61
+ help="number of CPU threads (default: all cores)")
62
+ ap.add_argument("--ccl", default="auto", choices=["auto", "cupy", "scipy"])
63
+ ap.add_argument("-k", "--kernel", default=None,
64
+ help="kernel FITS file, or 'none' (default: Gnuastro's built-in kernel)")
65
+ ap.add_argument("--khdu", default="1", help="HDU of --kernel")
66
+ ap.add_argument("--widekernel", default=None, help="wide kernel FITS file")
67
+ ap.add_argument("--whdu", default="1", help="HDU of --widekernel")
68
+ ap.add_argument("--label", action="store_true", help="write labels instead of a 0/1 mask")
69
+ ap.add_argument("--rawoutput", action="store_true", help="skip the INPUT-NO-SKY HDU")
70
+ ap.add_argument("--oneelempertile", action="store_true",
71
+ help="write SKY/SKY_STD with one value per tile (smaller output)")
72
+ ap.add_argument("-q", "--quiet", action="store_true")
73
+ hints = get_type_hints(NoiseChiselParams)
74
+ for f in fields(NoiseChiselParams):
75
+ names = ([SHORT[f.name]] if f.name in SHORT else []) + [f"--{f.name}"]
76
+ if hints[f.name] is bool: # flags, as in Gnuastro
77
+ ap.add_argument(*names, dest=f.name, action="store_const", const=True,
78
+ default=None, help="(default: off)")
79
+ else:
80
+ ap.add_argument(*names, dest=f.name, default=None, metavar="VAL",
81
+ help=f"(default: {f.default})")
82
+ ap.set_defaults(_hints=hints)
83
+ return ap
84
+
85
+
86
+ def main(argv=None):
87
+ from .io import run_fits
88
+
89
+ ap = build_parser()
90
+ a = ap.parse_args(argv)
91
+ hints = a._hints
92
+ params = {}
93
+ for f in fields(NoiseChiselParams):
94
+ v = getattr(a, f.name)
95
+ if v is True:
96
+ params[f.name] = True
97
+ elif v is not None:
98
+ params[f.name] = _parse_value(v, hints[f.name])
99
+ if f.name in FITS_ORDER and isinstance(params[f.name], tuple):
100
+ params[f.name] = params[f.name][::-1]
101
+ hdu = _hdu(a.hdu)
102
+ if a.kernel is not None:
103
+ params["kernel"] = None if a.kernel.lower() == "none" else _read_kernel(a.kernel, a.khdu)
104
+ if a.widekernel is not None:
105
+ params["widekernel"] = _read_kernel(a.widekernel, a.whdu)
106
+ out = a.output or os.path.splitext(os.path.basename(a.input))[0] + "_detected.fits"
107
+ t0 = time.perf_counter()
108
+ try:
109
+ res = run_fits(a.input, out, hdu=hdu, device=a.device, ccl=a.ccl,
110
+ numthreads=a.numthreads, label=a.label,
111
+ rawoutput=a.rawoutput, oneelempertile=a.oneelempertile,
112
+ verbose=not a.quiet, **params)
113
+ except OSError as e:
114
+ print(f"noisechisel-torch: error: {e}", file=sys.stderr)
115
+ return 1
116
+ if not a.quiet:
117
+ print(f"{res.num_detections} detections, S/N thresh {res.det_sn_threshold:.3f}, "
118
+ f"median std {res.med_std:.6g} -> {out} [{time.perf_counter() - t0:.2f}s]")
119
+ return 0
120
+
121
+
122
+ if __name__ == "__main__":
123
+ sys.exit(main())