daoTools is the application layer of DAO (Durham Adaptive Optics), a real-time control system for adaptive optics instruments. It builds on daoBase's shared-memory (dao.shm) and messaging primitives to provide everything needed to run an AO real-time control loop end to end: pixel calibration, wavefront-sensor centroiding (Shack-Hartmann, pyramid, correlation-based), matrix-vector-multiply reconstruction (CPU/BLAS and GPU/CUDA), leaky-integrator and modal loop control, plus the calibration, simulation, timing/latency, and data-conversion tools needed to operate and debug a pipeline.
It ships three things: a C function library (libdaoTools) that the real-time per-pixel/per-mode math lives in, 70+ command-line applications in apps/ that are thin real-time wrappers around it, and a set of PyQt5 GUIs in gui/ for live monitoring and control. Everything communicates over dao.shm, so any of these pieces can be mixed, replaced, or driven from your own code independently.
Full installation, usage, and API documentation is available on Read the Docs.
If you use DAO or daoTools in research or other published work, please cite daoBase, the core DAO library, using its DOI:
@software{barr_2025_17264152,
author = {Barr, David and
Cetre, Sylvain and
Connolly, John and
Thomas Davies},
title = {Durham-Adaptive-Optics/daoBase: Initial Release},
month = oct,
year = 2025,
publisher = {Zenodo},
version = {v0.0.1},
doi = {10.5281/zenodo.17264152},
url = {https://doi.org/10.5281/zenodo.17264152},
}Standard citation format:
Barr, D., Cetre, S., Connolly, J., & Thomas Davies. (2025). Durham-Adaptive-Optics/daoBase: Initial Release (v0.0.1). Zenodo. https://doi.org/10.5281/zenodo.17264152
libdaoTools (built from src/c/daoTools.c, declared in include/daoTools.h)
is the C function library most of the apps/ binaries below are thin
command-line wrappers around. It covers the operations that recur across an
AO real-time pipeline, operating directly on dao.shm IMAGEs:
- Pixel calibration —
daoToolsShmCalibrate/64(dark-subtract + flat-field, float and double precision),daoToolsShmCalibratePws(pyramid WFS variant). - Centroiding —
daoCentroidSpots(windowed center-of-gravity),daoCentroidSpotsRelative/RelativeRef(differential centroiding),daoCentroidSpotsCorrelation(windowed correlation search) and, when FFTW is available, its FFT-based counterpart (src/c/daoToolsCorrFFT.c,include/daoToolsCorrFFT.h) which searches the whole sub-aperture via one FFT round trip instead of a shift search;daoCentroidPwsfor pyramid WFS. - Control loop primitives —
daoToolsLeakyIntegrator/Doubleand the per-modedaoToolsLeakyModalIntegrator/Double,daoToolsHighPassFilter/Double,daoToolsCommandFilter. - Pyramid WFS pixel pipelines —
daoToolsShmExtract,daoToolsShmSubstractExtractand its normalized variants (...Norm,...NormA,...DualNorm, plus their...Finalizecompanions). - Misc. —
daoDmCombine(multi-channel DM command summing),daoDescrambleOcam2Image(OCAM2K scrambled-readout reordering),daoToolsImgNormalize,daoComputeChecksum,daoRtSetup/daoToolsEnableFTZ(real-time process setup, denormal flush-to-zero),daoToolsSetRtPriority(requestSCHED_FIFOreal-time priority with a safe, logged fallback -- see Real-time scheduling priority below), anddaoLogToFile(throttled, size-rotated file logging).
Most apps/*.c binaries call straight into this library, add SHM
attach/argument-parsing/real-time-loop boilerplate, and nothing else — the
library is where the actual per-pixel/per-mode math lives.
src/python/daoTools.py is a general-purpose Python AO toolbox, independent
of the C library above: a Fifo circular buffer, basic FITS read/write
helpers, pupil/circular-mask generation, center-of-gravity (cog), a
Hadamard basis, PSD/PSF computation (computePSD, computePsf,
computePsfRef), a ShackHartmannWFS simulation class and a pyramid-WFS
image simulator (pwfsImage). It predates and is separate from the
daoToolsLib/pipeline-scaffolding modules also under src/python/.
apps/ ships 70+ command-line tools. Full usage/flags are in the Sphinx docs
(waf build_docs, or docs/source/apps.rst); this is the index:
| Category | Tools |
|---|---|
| Wavefront sensing & centroiding | daoComputeCentroid (core Shack-Hartmann COG) and its Pws / Relative / RelativeRef / Correlation / CorrelationFFT variants, daoComputeCentroids.py / daoComputeCentroidsSlow.py (Python reference), daoComputeIntensityPws, daoPrepCentroidLut.py, daoPrepPwfs.py |
| Matrix-vector multiply | daoMvM (CPU, needs BLAS), daoMvMGPU (CUDA), daoMvM.py (Python reference) |
| Pixel calibration | daoPixelCalibrate / daoPixelCalibratePws, daoTakeBg.py |
| SHM utilities | daoShmMonitoring / daoShmMonitoring1Value, daoShm2Fits, daoFits2Shm.py, daoSnapshot.py, daoShmRate.py |
| SHM arithmetic | daoShmAdd, daoShmCombiner, daoShmConcatenate / daoShmConcatenateFine, daoAvgShm / daoAvgDoubleShm, daoStatShm, daoDownsample, daoPixelExtract, daoApplyGain |
| Loop & filter | daoLeakyIntegrator / daoLeakyIntegratorMap, daoClock, daoHighPassFilter, daoModesCutoff / daoModesCutoffFull, daoCommandFilter |
| Timing & latency | daoTimeDiff, daoTimeDiffNCurse, daoTimeDiffStat, daoSetLatency, daoPlotLatency.py |
| Data conversion & I/O | daoNpy2Shm.py, daoTakeDataCubeFITS.py, daoTakeDataCubeNPY.py, daoDescrambleOcam2, daoDMSend, daoRandImageU16Write, daoRandWriter / daoRandWriterSync |
| Simulation | daoReconstructor.py (Python MVM reference), daoTurbulenceSimulator.py, daoPwfsSimulator.py, daoNoisyPsfGenerator.py |
| Logging & communication | daoRecvLogs.py, daoSendLogs.py, daoLogToScreen.py, daoProxyLog.py, daoSendCommand.py, daoStrCmd.py / daoReadStr.py / daoWriteStr.py, daoRemoteShmFileServer.py, daoRedisCheck.py, daoDAQCli.py |
| Real-time display (RTD) | daoImageRTD.py / daoImageRTDFloat.py, daoBarRTD.py, daoShRTD.py, daoWavefrontRTD.py, daoPlotRTD.py |
gui/ (PyQt5 + pyqtgraph, source in docs/source/gui.rst) ships standalone
viewer/control applications, each reading its target straight from a dao.shm
stream — pass the SHM name(s) on the command line, no config file needed:
| Category | Tools |
|---|---|
| Image / SHM viewing | daoShmViewer (flagship multi-panel viewer, dark by default (--light for light mode), built-in DAQ session panel, and a "SHM Latency" tab that launches daoTimeDiff in its own tmux session and live-plots the AVG/RMS it publishes for any two SHMs picked from the file list), daoImDisp / daoImgDisp (lightweight single-image viewers), daoRTDMagic (OpenGL-textured, lowest-latency image RTD), daoRemoteShmViewer (mirrors a SHM stream from another host via daoRemoteShmFileServer.py) |
| DM control & display | daoDmCtrl (modal control of one DM channel through its M2A matrix — any modal basis, not analytic-only), daoDmChannelsCtrl (overview of all DM command channels at once, opens daoDmCtrl per channel), daoDmDisp / daoDmDispNoMap (read-only actuator display, mapped / raw bar chart) |
| Loop control | daoLoopDisp / daoLoopCtrl (generic open/close-loop and gain/leak control panels — point at any loop's state/gain/leak scalar SHMs, default lpCmd/lpGain/lpLeak) |
| Wavefront & slopes display | daoWfDisp / daoWfDispMap (reconstructed phase, plain or over an illumination map), daoSlopesDisp (X/Y slope quiver plot), daoShDisp (colour-coded slope grid), daoRTDMagicSH (low-latency Shack-Hartmann RTD), daoTtDisp (tip/tilt scatter monitor), daoBarDisp (generic real-time bar chart of any 1-D SHM vector) |
| Latency measurement | daoTimeDiffDisp (standalone SHM-to-SHM latency GUI — takes the two SHMs on the command line, Start/Stop launches daoTimeDiff in its own tmux session and live-plots its AVG/RMS window as a scatter + histogram; same measurement as daoShmViewer's "SHM Latency" tab, without the rest of the viewer) |
| Logging & telemetry | daoLogMonitor (live, filterable ZMQ log viewer), daoShmTelemetryConfigurator (graphical daoDAQ YAML editor, embedded in daoShmViewer) |
| Reusable widget | daoProcessWidget (live process-status widget — tmux session alive/dead, CPU load, last update — embedded in other GUIs) |
daoBase should be installed. See https://github.com/Durham-Adaptive-Optics/daoBase
waf configure fails without these - every app in apps/ links at least protobuf,
zmq and CLI11.
Ubuntu/Debian:
sudo apt install libcli11-dev libcfitsio-dev libyaml-cpp-dev libfmt-dev libzmq3-dev \
libprotobuf-dev protobuf-compiler libncurses-dev
RHEL/Fedora/CentOS:
sudo yum install cfitsio-devel yaml-cpp-devel fmt-devel zeromq-devel \
protobuf-devel protobuf-compiler ncurses-devel
(CLI11 has no RHEL package as of writing; configure falls back to looking for
a vendored CLI11.hpp/CLI/CLI.hpp header if pkg-config can't find it.)
brew install openblas pkg-config protobuf cfitsio yaml-cpp fmt zeromqNote: Homebrew installs OpenBLAS as
openblas.pc. Thewafconfigure script automatically checks for bothblasandopenblaspkg-config packages, so no additional configuration is needed.
waf configure auto-detects each of these and silently skips the app(s)
that need it if missing - not having them is not an error, just a smaller build.
| dependency | apt package | enables | skipped without it |
|---|---|---|---|
| BLAS (any implementation) | libopenblas-dev |
daoMvM (CPU real-time matrix-vector multiply) |
daoMvM |
CUDA toolkit (nvcc + cudart) |
see NVIDIA's install docs | daoMvMGPU |
daoMvMGPU |
| FFTW, single and double precision | libfftw3-dev |
the FFT-based correlation centroider (daoToolsCorrFFT, daoComputeCentroidCorrelationFFT) |
that centroider only - daoComputeCentroidCorrelation (the windowed-search version) is unaffected |
Check waf configure's output for lines like BLAS detected: enabling BLAS build. / FFTW not found ... skipping FFT correlation centroider. to see
what your machine actually got.
We recommand to use magicPlot Some of our plot tool uses magicPlot (optional)
pip install magicPlot
Every real-time loop in apps/ requests SCHED_FIFO scheduling once at
startup via daoToolsSetRtPriority(priority) (daoTools.h, e.g. priority
93), so the OS doesn't preempt it for ordinary processes.
That request can be silently denied: Linux caps how high a priority a given
user may request (the rtprio resource limit), and it defaults to 0 on
most distros. Before daoToolsSetRtPriority existed, every tool called
sched_setscheduler() directly with no check on its return value -- if the
request was denied (EPERM), the loop just kept running at normal priority
with no indication at all. That's dangerous for a control loop with little
stability margin: it becomes vulnerable to timing jitter from anything else
that uses the CPU, even a lightweight, read-only monitor GUI.
daoToolsSetRtPriority still tries for the requested priority first --
identical behaviour to before when it succeeds -- and only on failure checks
this user's actual rtprio limit and falls back to the highest priority
currently allowed. Either way it logs which priority it actually got
(daoInfo on success, daoWarning on fallback/failure), so the operator
always knows.
Add a line to /etc/security/limits.conf (or a file under
/etc/security/limits.d/), then start a new login session (log out/in, or a
fresh login shell) for it to take effect:
<username> - rtprio 99
or for everyone in a group:
@rtgroup - rtprio 99
Confirm it applied with:
ulimit -r
No new platform support is added or removed here -- it only changes how the existing, Linux-oriented real-time code behaves:
sched_setscheduler()/SCHED_FIFOare POSIX real-time scheduling calls; none ofapps/'s real-time-priority code has ever been guarded for Windows, so real-time scheduling in this codebase has always been Linux-only in practice (same asmlockall()and thepthread_setaffinity_npCPU-pinning code used elsewhere in the tree).- macOS:
RLIMIT_RTPRIO(the fallback-limit lookup) doesn't exist on macOS/BSD, sodaoToolsSetRtPriorityguards that part behind#ifdef RLIMIT_RTPRIO. This means the code now compiles cleanly on macOS (a plain, unconditionalRLIMIT_RTPRIOwould have been a hard compile error there) and logs a clear warning ifsched_setschedulerfails, instead of silently running non-real-time as before. Functionally unchanged from before this fix -- just no silent failure and no compile break. - Windows: unaffected either way --
sched_setschedulerhas no Windows equivalent and this codebase was never built/run there.
daoDmDisp.py lays out the channel displays followed by the combined command
in three columns, adding rows as needed. Large grids scroll. The default is
four channels; use -n / --channels for another count:
daoDmDisp.py -s dmCmd -m dmMap # 4 channels
daoDmDisp.py -s dmCmd -m dmMap -n 6 # 6 channels
daoDmDisp.py -s dmCmd -m dmMap -n 8 --light
daoDmDisp.py -n 6 -l Flat,Loop,Turbulence,Pokes,Focus,TipTiltFor -n 6, the display reads dmCmd00 through dmCmd05, plus the
combined dmCmd stream and the dmMap actuator map from /tmp/*.im.shm.
These streams must already exist. Default labels are Flat, Loop, Turbulence,
Pokes, then Channel 04, Channel 05, etc. Custom labels must supply one name
per channel. The layout is generated in Python, so no separate .ui file
is needed for each channel count.
The C/C++ tools use daoBase's primary shared-memory API
(daoShmCreate, daoShmOpen, daoShmSetData, etc.). Build and install
daoBase with that API (commit 8f64d74 or later) before rebuilding daoTools;
older installed headers/libraries do not provide these names. Python callers
keep the same public shm interface.
See the migration regression tests for the exact rename mapping, before/after numerical checks, and validation limits.
Configuration continues when optional C++ application dependencies are missing. The build prints the name of each skipped program and its missing or unusable dependencies:
| Program | Required optional dependencies |
|---|---|
daoDAQ |
CFITSIO, CLI11, fmt, ZeroMQ, protobuf, yaml-cpp |
daoDownsample |
CLI11, protobuf, ZeroMQ |
CFITSIO must provide the unsigned 64-bit constants used by daoDAQ; configuration
checks both its headers and library. CLI11 must provide CLI/CLI.hpp.
The core C library and other tools remain enabled. Existing BLAS, CUDA, and
FFTW checks continue to control their respective targets.
After installing a missing dependency, rerun waf configure and waf build
to enable the corresponding programs. Skipping a program does not uninstall
an older copy that is already present.
waf configure --prefix=$DAOROOT
waf
waf install
Run waf build_docs to build the Sphinx HTML documentation (outputs to docs/build/html).
waf build_docs
To clean the built documentation:
waf clean_docs
It is possible to create just the shared memory for an example. See examples below
The first example is a simple C code increasing a counter in the shared memory. This software clock can be used to synchronize different program using built-in semaphore in the DAO SHM
can be skipped if SHM already created
import daoShm
import numpy as np
clockShm=daoShm.shm('/tmp/demoClockShm.im.shm',np.zeros((1,1)).astype(np.uint32))
Now let's run the clock at 1.5kHz in a new console
daoClock -L demoClockShm 1500
This example is a C program writing in the shared memory at a specific rate. The program is waiting for a new value in another shared memory. We can use the previous example and use the clock as the trigger
can be skipped if SHM already created
import daoShm
import numpy as np
shm=daoShm.shm('/tmp/demoShm.im.shm',np.zeros((100,100)).astype(np.float32))
clockShm=daoShm.shm('/tmp/demoClockShm.im.shm',np.zeros((1,1)).astype(np.uint32))
Now run in a new console the program writing at the clock rate in the share memory random values
daoRandWriterSync -L demoShm demoClockShm