Skip to content

Merge in Prerelease - #65

Open
g7gpr wants to merge 78 commits into
g7gpr:feature/systemdfrom
CroatianMeteorNetwork:prerelease
Open

Merge in Prerelease#65
g7gpr wants to merge 78 commits into
g7gpr:feature/systemdfrom
CroatianMeteorNetwork:prerelease

Conversation

@g7gpr

@g7gpr g7gpr commented Aug 23, 2026

Copy link
Copy Markdown
Owner

No description provided.

Cybis320 and others added 30 commits June 28, 2026 22:36
When the watchdog force-kills a hung BufferedCapture (SIGKILL on bc.pid only), its
RawFrameSaver child is orphaned and loops forever on a shared exit Event that is never
set, holding its inherited ~450MB frame buffer. Across watchdog restarts on a multicam
box this accumulated hundreds of orphaned processes (291 vs ~35 baseline) -> ~14 GiB
anon -> OOM.

Arm PR_SET_PDEATHSIG=SIGKILL in RawFrameSaver.run() so an orphaned saver is killed the
instant its parent dies, with a getppid()==1 race guard. Adds Misc.setParentDeathSignal.
releaseResources() calls pipeline.set_state(Gst.State.NULL), which blocks indefinitely
when rtspsrc is wedged on a connected-but-unresponsive camera (RTSP TEARDOWN never
completes). That froze the capture loop's heartbeat for 180s until the watchdog
force-killed the process - the event that orphans RawFrameSaver.

Wrap both set_state(NULL) calls in runWithTimeout (GST_TEARDOWN_TIMEOUT=10s). A stuck
teardown is abandoned (pipeline left to GC) so capture keeps its heartbeat and is never
force-killed for a hang.
Two compatibility gaps in the PDEATHSIG fix:
- setParentDeathSignal default was signal.SIGKILL, evaluated at import - absent on
  Windows, breaking Misc.py import there. Use numeric 9.
- Under the forkserver start method (Python 3.14's new Linux default) the OS parent is
  the fork-server, not BufferedCapture, so PR_SET_PDEATHSIG fires on the wrong death.

Capture the logical parent PID in RawFrameSaver.__init__ (runs in the parent under
fork/spawn/forkserver alike) and self-terminate in the idle loop if it dies. This is
start-method- and platform-agnostic; PDEATHSIG remains the instant fast-path under
fork/spawn.
Add Kht.pyxbld so the KHT wrapper can rebuild via pyximport
Auto levels are now turned on when SkyFit2 starts, so images appear
properly stretched immediately instead of using the full bit range.

The histogram region is no longer locked while auto levels are active:
dragging the region (or Ctrl+clicking to set a level) switches to manual
mode, keeping the auto-determined levels as the starting point. Ctrl+A
still toggles auto levels on and off.

Full-range setLevels calls on station change and state load now only
apply in manual mode so they don't override the auto stretch.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
img_zoom.changeHandle() reloads the zoom image via setImage() without
levels, so pyqtgraph assigns it independent min/max levels. With auto
levels on, the histogram was updated before the zoom reload and never
re-emitted, leaving the zoom window stretched differently from the main
image. The manual path had the same latent issue: setLevels() with an
unchanged full range early-returns in setRegion() without emitting.

Explicitly reapply the histogram levels to both image items after the
reload, matching the existing pattern in updateImage handling.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Promote the exception handlers around night product generation in
Reprocess (calibration report, observation summary, timelapse, flat,
shower association, FOV KML, flux files, field sums, FT archiving,
config audit, timestamp plot) from log.debug to log.warning so failures
reach the uploaded night logs instead of being silently swallowed.

Route ConfigReader validation warnings (FPS clamp, FOV check, jpg/png
ranges, binning factor/method, band ratios, upload disabled on the
default station code) through logging instead of print(), so they also
land in the night logs with a level and timestamp.

Add an on-screen key legend to FRbinViewer (toggled with 'h') - the
keys were previously only documented in the --help epilog.

Document the runnable RMS/ pipeline modules in Utils/README.md, which
previously only cataloged the Utils/ scripts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ArchiveDetections, UploadManager, DownloadPlatepar, and
CaptureModeSwitcher have hardcoded test stubs in their __main__ blocks
rather than user CLIs, so following the README could trigger unintended
test file/network operations. Mark them as internal instead.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…arden prctl helper

- createGstreamDevice cleanup set_state(NULL) now time-bounded (blocking finding)
- os.kill(pid, 0) probe gated to POSIX, catches ProcessLookupError only
- setParentDeathSignal: document thread-death and subreaper semantics, resolve
  libc via find_library (musl), log instead of silent failure
- Replace local _timedCall with runWithTimeout; log late teardown unwinds
RMS/ClearSkyDetector.py was swept into PR #762 unannounced. It is a
standalone work-in-progress tool: nothing in RMS imports it, and it
cannot currently be imported since it depends on
RMS.ConfigReader.findConfigInDir, which does not exist in this repo.
Development continues out of tree.
…-default

Enable auto levels by default in SkyFit2
bugfix for #929 malformed kml file in some cases
…amesaver-oom

Fix OOM caused by orphaned RawFrameSaver processes after watchdog force-kills
…tector

Remove ClearSkyDetector, accidentally included in a SkyFit refactor PR
Reject a recalibration fit that matched fewer than a minimum fraction of
the detected stars (default 0.6, configurable via
recalibration_min_match_fraction). checkFitGoodness only inspects the
residuals of the stars that did match, so a sparse spurious subset at a
wrong pointing could pass, get stamped auto_recalibrated, and chain
forward as the seed for the next FF - walking the pointing away over a
cloudy night. The gate applies even with force_platepar_save, so the
last-resort coarse-radius fallback can no longer save such fits either.

The gate and the photometry fit now use the matched stars from the last
successful fit iteration instead of whatever the last matchStarsResiduals
call produced - a failed tighter-radius iteration used to overwrite
matched_stars with matches of a platepar that was never accepted (and
could leave it unbound under force_platepar_save).

SkyFit2's manual pointing-only recalibration disables the gate since the
result is user-inspected.
Comment out stale rmem_max/wmem_max lines in /etc/sysctl.conf that load
after the drop-in at boot and override it.
…age-gate

Add star coverage gate to per-FF recalibration
Make UDP buffer sizes persist across reboots
pyqtgraph <= 0.11 HistogramLUTItem.setImageItem() always passes its
getLookupTable method to ImageItem.setLookupTable(), and newer versions
still do so for non-grayscale gradients. _composeDisplayLut() assumed an
array and crashed with IndexError on np.asarray(callable). Resolve a
callable base LUT at render time, as pyqtgraph does, and compose
gamma/inversion on its result.
…e-lut

Fix SkyFit2 crash on older pyqtgraph (callable LUT)
dvida and others added 30 commits August 6, 2026 13:17
The comments added with the catalog LM fix were far longer than anything
else in SkyFit2 and cited file:line numbers that will go stale. Cut them
down to the usual one to three lines.

Also drop the claim that the post-save sync stores "the value actually
written": config_catalog_lm is unrounded, while the file holds one
decimal. The 0.05 tolerance in isConfigModified() covers the difference.

The R and F keys change cat_lim_mag too, but did not refresh the save
state, so the Save Config button stayed disabled and the File Manager
button stayed grey after a keyboard LM change. Refresh it there as well.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Saving the config from SkyFit2 wrote [Calibration] catalog_mag_limit,
which it had no business touching. The LM is a working value in the
tuner, not something the user asked to persist.

Worse, it was written as cat_lim_mag - 1.0, so it ratcheted: a config
with LM 6.5 was loaded into the LM spinbox, saved back as 5.5, loaded as
5.5 on the next start, saved as 4.5, and so on down to the 3.0 floor.
Every open and save cycle cost recalibration another magnitude.

Drop the Calibration section from the surgical config writer, so only
the [StarExtraction] keys the Save Config button advertises are touched.
The LM the tuner finds stays a session value, as before.

This also removes the modified/save-cycle plumbing for the LM, which
only existed to support writing it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The catalog limiting magnitude serves two roles in SkyFit2. It is a
viewing knob, nudged with the spinboxes and the R/F keys to show more or
fewer stars, and it is the result of Phase 3 of the star detection
tuner, which finds the LM that best matches the confirmed detections.
Saving the config wrote the knob, so glancing at fainter stars and
hitting save silently changed the LM that the nightly recalibration
matches at.

Write the tuned value instead, and only that. It is offered in the save
dialog as a checkbox, on by default, showing the old and the new value.
Unchecking it leaves the config LM alone.

The dialog now lists every value the save will write as old -> new, so
the star extraction parameters are no longer applied sight unseen
either.

The tuned LM also counts as an unsaved change on its own, so a tune that
only moves the LM still enables the Save Config button. The working LM
never does, which is what keeps the value from drifting across repeated
save cycles.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The star detection tuner optimizes for interactive work, where a few
hundred stars are plenty, but the nightly processing needs a deeper
list. Saving the tuned value could therefore leave a station worse off
than the shipped default of 800.

Floor the saved value at MIN_CONFIG_MAX_STARS. The working value in the
GUI is left alone, so the detection view does not change under the user.
The save dialog says when the floor, rather than the tuning, set the
value that gets written.

The modified check compares the floored value as well, so a config that
already holds 800 is not reported as modified forever by a smaller
working value.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The preview was a stack of sentences, which made it hard to see at a
glance which values a save would actually change. Put it in a grid of
parameter, current value and new value, and bold the new value wherever
it differs. Values that stay the same are shown once, without an arrow.

The catalog LM checkbox is now the parameter cell of its own row, so it
lines up with the rest instead of trailing its values in the label.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…g-offset

Save only the tuned catalog LM, and show what a config save writes
…d-artifacts

Ignore Cython-generated C sources by pattern and QueuedPool backup pickles
…lity

Make night product failures visible and improve tool discoverability
The previous version held F_scale in the parameter vector but missed two
sites on the NN/RANSAC path, where Nelder-Mead (unlike LM) drifts the flat
coordinate:

- the final "apply best RANSAC params" extraction persisted the drifted
  best_res.x[3] into self.F_scale;
- the recursive final matched-pair fit was called without forwarding
  fixed_scale, so it re-fit the scale freely.

Now every extraction, scoring temporary, warm-start temporary and the
iteration-callback copy reads the held f_scale_fixed instead of the
parameter, and fixed_scale is forwarded through the recursive fitAstrometry
call. Since the residual functions already make the objective independent of
the scale parameter, the drifting coordinate has no effect on the result.

Verified on AU000A (radial9-odd) synthetic recovery for BOTH cost paths:
  use_nn_cost=False: fixed_scale=False recovers (0.000 px), True holds
  use_nn_cost=True : fixed_scale=False recovers (0.001 px), True holds (no drift)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The radial fixed_scale fix gated every NN/RANSAC extraction and residual
site, but left the two matched-pair (use_nn_cost=False) extractions bare:
the fwd-rev loop and the final joint refinement wrote self.F_scale from the
fitted vector. They held the seed only because the patched residual zeroes
the F_scale Jacobian column and least_squares leaves that coordinate put --
an implicit reliance on optimizer null-space behavior, on the primary path
the "Fixed scale" GUI checkbox actually exercises (fitPickedStars ->
use_nn_cost=False).

Gate both with the same `f_scale_fixed if fixed_scale else abs(...)` pattern
used everywhere else so the hold is explicit. No behavior change: verified on
a radial7-odd synthetic recovery -- fixed_scale=True holds the seed,
fixed_scale=False recovers truth at 0.000 px.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Honor fixed_scale in the radial fitAstrometry paths
tryQuickAlignment and getInitialParamsAstrometryNet save and restore
self.fixed_scale around their work, but never modify it in between and never
forward it into their fitAstrometry(use_nn_cost=True) calls -- so the pair is
dead code that reads like unfinished "Fixed scale" wiring.

Not forwarding it is correct, not a bug: both methods (and the AutoPlatepar
NN callers) fit from scratch with first_platepar_fit=True, i.e. initial
acquisition, where the scale must be free to be determined. Pinning it there
would degrade acquisition when the box is checked. The "Fixed scale" checkbox
is honored where it belongs -- refinement via fitPickedStars, which already
passes fixed_scale=self.fixed_scale.

Remove the dangling save/restore so the code matches the intent. No behavior
change (fixed_scale was never used in these blocks).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…estore

Remove dead user_fixed_scale save/restore in SkyFit2 acquisition paths
Handing each extractor its own ~236 MB snapshot copy per block (added to
avoid a refill race under forkserver/spawn) had no concurrency bound: on
noisy nights slow extractions piled up, each pinning a copy, and the OS
OOM-killed capture mid-night on memory-limited Raspberry Pis.

Revert to the original design where the extractor reads the LIVE
ping-pong buffer, so memory stays fixed at the two existing frame
buffers. Pass the mp.Array base (not a numpy view) so it crosses to the
child by handle under fork and forkserver/spawn alike instead of being
pickled by value; the extractor rebuilds its view via .get_obj() since
the live base is a locked Array. With two buffers the grace window is
about one block (less in practice, since the handshake is released and
the FF/JPG saved before the extractor starts), so a rare extraction that
outruns it lets capture overwrite the block mid-read and corrupts that
one FR clip - the accepted trade-off for bounded memory; FF files and
detection are unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
NumPy 2.0 removed the implicit conversion of a size-1 array to a Python
scalar via int()/float(). np.fromfile(..., count=1) returns a length-1
array, so int(np.fromfile(...)) raises "only 0-dimensional arrays can be
converted to Python scalars" on NumPy >= 2.0. This surfaced when Trixie
(Python 3.13) pulled numpy>=2.3, breaking FRbinViewer via FRbin.read.

Index the size-1 array before converting (int(np.fromfile(...)[0])),
matching the form already used at FRbin.py:176 and in StarCatalog.py.
Applied to all 39 affected sites across the binary readers: Vid.py,
FFbin.py, FTfile.py, FieldIntensities.py, and FRbin.py.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…-fix

Fix mid-night OOM (fixed-memory live-buffer extractor) and NumPy 2.x binary readers (#959)
checkWhiteRatio() aborts all line finding when too many pixels pass
the detection threshold, with only a debug-level line that reaches
the nightly log file but never the console. On stations where the
video path smooths per-pixel temporal noise (e.g. software re-encoded
RTSP streams), stdpixel collapses while the maxpixel noise floor
stays, the k1*stdpixel + j1 threshold lands below that floor, and
every image of a clear night gets rejected - the station is
effectively blind to meteors while the console shows nothing but
'detected meteors: 0'.

Promote the first rejection (and every 100th) to a warning with the
measured ratio and a hint that k1/j1 may be too low for the camera,
so affected operators can notice and correct the thresholds. The
rejection counter is per worker process, so it is used only to rate
limit the warning and is not printed as a run-wide total.
Warn when the white-ratio check silently skips meteor detection
…candidates

Add star candidate and PSF-fit visualization
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants