Skip to content

About

Linux software for operating Alcor Cyclope seeing monitors

Resources

Stars

0 stars

Watchers

1 watching

Forks

Latest commit

 

History

31 Commits

Folders and files

Repository files navigation

cyclop

tests

Linux replacement for Alcor System's Windows-only SeeingMonitor_Cyclop software, written for the MMTO MiniCyclop (no heater or RS232). It drives the camera directly over GigE Vision, measures Polaris' image motion, and publishes seeing straight to redis.

How it works

  1. When the Sun is below max_sun_alt (-5°), it grabs full frames (2592×1944) until Polaris is detected in several consecutive frames.

  2. It switches to a 640×480 region around the star and centroids every frame (132 fps), moving the region when the star drifts near an edge. If the star is lost for lost_timeout s (clouds), or fewer than min_valid_fraction of the frames over that time have a valid centroid (under thick cloud noise spikes alone pass ~3%), it goes back to full-frame searching. A block whose position drifts less than min_drift px/min (Polaris moves ~2.2 px/min on this camera, though a single block's fit in poor seeing can come out as low as ~0.4) tracked a fixed defect such as a flickering hot pixel: it is discarded, and later searches ignore that spot. If the camera delivers no frames at all for frame_timeout s, or drops our control connection, it is closed and reopened. Acquisition and analysis run in parallel: a thread copies an 81×81 cutout around the star from each frame into chunks of up to 256 frames (chunk_frames, or chunk_seconds), and the main loop centroids a whole chunk at once with vectorized numpy (about 0.1 ms per frame) while the next one fills.

    Positions come from cross-correlating each frame with a Gaussian (centroid = "xcorr", a matched filter of sigma xcorr_sigma) within xcorr_reach px of the star's last position, with the peak refined by a 3-point Gaussian fit along each axis. For a ~2 px star this has 2-4× less noise than a thresholded centre of mass (centroid = "moments", the original method) and no pixel-phase bias: read noise in the wings, which the moments let into σ, is weighted down. On sky (2026-10-02) the moments gave seeing ~35% above the MMT wavefront sensors, while the cross-correlation agreed with them (2.27/2.36" vs WFS 2.27") and with photutils aperture centroids at r ≈ 2 FWHM. Frames are kept when both the star's peak S/N (min_snr) and the correlation S/N (xcorr_min_snr) pass.

  3. Every n_samples (3000) centroids, it drops samples more than clip (5) robust sigmas from the drift-removed median (a frame that centroided on noise would otherwise dominate σ), removes the slow drift of Polaris (linear fit in time) and computes:

    local seeing ["] = 13.58031732652 × (σx^1.2 + σy^1.2) / 2      σ in pixels
    zenith seeing    = local × cos(90° − latitude)^0.6
    r0 [mm]          = 550 nm / zenith seeing
    

    These relations were recovered from the Windows software's own Data.bin history (300,000 measurements) and reproduce its reported values to ~1e-10 given the same σ.

    Flux is the background-subtracted sum around the star, corrected for a sensor pattern that repeats every 4 columns: as Polaris drifted across columns the raw flux varied by ±9%, cycling faster or slower with its speed in x. The correction divides each frame's flux by 1 + Σ aₖ cos(kπx/2) + bₖ sin(kπx/2) at the star's full-frame column x, with column_flux_terms = [a1, b1, a2, b2] fitted on sky (2026-10-02/03); [] turns it off.

  4. Results go to:

    • Seeing_Data.txt / Last_Seeing_Data.txt in output.data_dir, in the same format as the Windows software;
    • redis keys seeing_monitor_{seeing,flux,r0,measurement_timestamp} (set + publish), as minicyclop's tcs_logger did.

Install

conda create -n cyclop -c conda-forge python=3.14 numpy redis-py pytest
pip install -e .

Camera access needs Aravis with GObject introspection, built into the same environment (conda-forge has no Aravis package); see docs/aravis.md.

Use

cp config.example.toml config.toml
cyclop -c config.toml camera-info                 # check the camera is reachable
cyclop -c config.toml run --simulate --ignore-sun --no-redis -n 2    # dry run with a fake star
cyclop -c config.toml run                         # the real thing
cyclop -c config.toml run --gain 10 --exposure 2000   # try other camera settings (dB, us)
cyclop replay ~/path/to/*_Motion.txt              # reduce Windows motion files with this code

Running as a service

In production cyclop runs unattended as a systemd user service (systemd/cyclop.service), reading ~/cyclop_linux/config.toml. It idles in daylight and starts measuring once the Sun is below max_sun_alt, so it can be started or restarted at any time of day. systemd restarts it 30 s after a crash. Install:

cp config.example.toml config.toml                # then edit; at least [output] and [redis]
mkdir -p ~/.config/systemd/user
cp systemd/cyclop.service ~/.config/systemd/user/
systemctl --user daemon-reload
systemctl --user enable --now cyclop
sudo loginctl enable-linger $USER                 # start at boot without a login session

Stop any other program using the camera first (e.g. a cyclop run started by hand). Day-to-day:

systemctl --user status cyclop
systemctl --user restart cyclop                   # after editing config.toml or updating the code
systemctl --user stop cyclop                      # frees the camera
journalctl --user -u cyclop -f                    # follow the log
journalctl --user -u cyclop --since "18:00"

At the MMTO it runs on a small Debian 13 PC with two onboard Ethernet ports, one for the site LAN and one dedicated to the camera. On a desktop install, mask the sleep targets so the machine never suspends when idle (sudo systemctl mask sleep.target suspend.target hibernate.target hybrid-sleep.target).

From 2026-10-02 to 2026-11-08 it ran under WSL2 on the Windows PC. There the service runs only while WSL is up, which needs systemd=true in /etc/wsl.conf, automatic Windows login, and a startup item that keeps WSL running (e.g. @start /b wsl --exec dbus-launch true in the Startup folder, with vmIdleTimeout=-1 in .wslconfig).

Frame timing

Each frame is timestamped with the host clock when it arrived (Aravis' buffer system timestamp), so frames that wait in the queue while the analysis catches up keep their true times. Frames the camera produced but the analysis never saw (queue full, incomplete) are counted from gaps in the GigE Vision frame ids. Every block's log line reports the processed rate, the camera rate, the percentage dropped and the mean analysis time per frame, which must stay under the camera's frame period (about 7.5 ms at the 640×480 region's maximum of 132 fps) to use every frame. Where the network driver counts packets per CPU (ethtool -S, e.g. hv_netvsc under WSL2), the line also gives the share of the block's packets each CPU received.

Web interface

cyclop run also serves read-only status pages at http://<host>:8080/ ([web] in the config, --web-port, or --no-web). They loosely follow the Windows GUI:

  • Capture: the live camera frame with the star marked, its histogram, saturated-pixel count, brightest pixel, a zoom on the star, the camera settings, and the processed versus camera frame rate with the fraction of frames dropped;
  • Status: UTC/local/sidereal time, Sun elevation, and the last local and zenith seeing and r0;
  • Plots: the Windows "Output results" tabs: zenith seeing and flux versus time (1h to 1M, history from <data_dir>/Seeing_Data.txt plus any web.history_files, such as the Windows software's log), a histogram of zenith seeing over the same range with its median, and the star motion and FWHM of the latest centroids;
  • a running log at the bottom.

Tabs can be linked to directly, e.g. #status or #plots/motion. The page is self-contained (no external scripts), so it works without internet access. Everything is controlled from the CLI and config file; the web interface never changes anything.

Notes

  • Only one program can control the camera at a time: stop SeeingMonitor_Cyclop.exe (or the cyclop service) before running another cyclop against the real camera.

  • The camera is at 192.168.2.59 on a dedicated 192.168.2.0/24 interface (host 192.168.2.2, no gateway), with Energy-Efficient Ethernet turned off (ethtool --set-eee <iface> eee off). Under WSL2 this needed networkingMode=mirrored in .wslconfig, and broadcast discovery did not reach WSL, so camera.address is set by IP.

  • Under WSL2 each stream's UDP port hashes to one of the virtual NIC's receive channels, so one CPU takes all its packets, and streams received on CPU1 or CPU3 dropped 3–8% of frames against 0.3–1% on CPU0 or CPU2. avoid_rx_cpus = [1, 3] under [camera] made cyclop restart a new stream (up to max_stream_restarts times) until it landed elsewhere, for a median of 0.8% dropped. On native Linux leave the list empty, and if drops are high, steer the NIC's interrupts instead.

  • Jumbo frames are not required for the 640×480 tracking region but help full-frame searches. The MMTO host uses MTU 9000; cyclop negotiates the GVSP packet size to fit.

  • Give the GigE stream room to ride out host pauses: cyclop asks for an 8 MB UDP receive buffer (socket_buffer_mb), which the kernel caps at net.core.rmem_max, so raise that (and the input backlog), e.g. in /etc/sysctl.d/90-cyclop.conf:

    net.core.rmem_max = 16777216
    net.core.netdev_max_backlog = 5000
    

    With the ~200 KB default, WSL2 lost 20-50% of frames to missing packets.

About

Linux software for operating Alcor Cyclope seeing monitors

Resources

Stars

0 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages