Svenesis LightCurve
Version 1.0.0 – GPL-3.0-or-later
🚧 Current version 1.0.0. Not yet in the official Siril Script Repository – download it directly from GitHub.
LightCurve does exoplanet transit photometry inside Siril – in the spirit of EXOTIC (NASA’s Exoplanet Watch pipeline) and HOPS. Point it at the folder holding one night’s sub-exposures of an exoplanet host star. It measures how that star’s brightness changed relative to other stars in the same field, removes the systematic trends it can account for, fits a transit – and tells you whether the dip is real or whether you are looking at a cloud.
Who does what
The division of labour is deliberate, and it explains most of the design.
| Responsible for | |
|---|---|
| Siril | Staging, calibration, two-pass registration, star detection, the plate solve and per-frame quality – the things it is demonstrably good at |
| The script | The flux itself: every star re-centroided per frame from its predicted position (the “follow star” Siril’s light_curve lacks), subpixel apertures against a sigma-clipped sky annulus, the aperture chosen by point-to-point noise, errors from the CCD equation with every term measured |
| And the decisions | Which star is the target, how to remove the airmass ramp without eating the transit depth – and whether to claim anything at all |
Measured on the same drifting 142-frame run: this engine keeps 140 points at 6.3 mmag where Siril’s light_curve kept 67. The latter remains intact as the loud fallback – if the engine measures fewer than 30 per cent of the frames, it says so and hands over.
Validated against EXOTIC
Tested on EXOTIC’s own sample data (HAT-P-32 b, 142 frames): Rp/R★ = 0.1525 ± 0.0064 against EXOTIC’s published 0.1541 ± 0.0033 – 0.2 σ apart, at the same residual scatter (0.58 % vs 0.55 %).
Both depth conventions are reported and labelled: the limb-darkened central depth the fit measures, and the (Rp/R★)² that EXOTIC, HOPS and AstroImageJ quote – about 20 per cent shallower on a solar-type star. It is that one you should compare with theirs and with the archive.
Why not Siril’s own light-curve tool?
Siril’s light-curve workflow produces a light curve; this script produces a measurement. In between sit the follow-star re-centroiding, the drift handling and the comparison-star screening – plus the analysis the native tool ends before: BJD_TDB, a simultaneous limb-darkened fit, a calibrated significance, O−C and the AAVSO file.
Calibration finds its own frames
Nothing has to be prepared. Point at the subs – or at any folder above them, the scan is recursive and sorts by FITS header – and, once, at the folder where your reusable darks live. The flats are found beside the lights in the N.I.N.A. layout, grouped by exposure, gain, temperature, binning, image size and camera, stacked into masters and cached for the next run. The pixel work is Siril’s calibrate – there is no bias, dark or flat arithmetic in this script, for the same reason there is no photometry in Siril.
What gets refused is said out loud – because a master that was found and then rejected leaves a run that looks exactly like one where no master existed:
| Refused | Why |
|---|---|
| Wrong exposure (dark) | A 3-second dark on 60-second lights removes 5 % of the dark current, leaves the rest standing, and lays its own read noise onto every frame. Reported with both numbers – and with what it would have done |
| Wrong temperature | Darks are grouped by temperature; averaging −10 °C and −20 °C together is right for neither. Bias is not split – it is pure read noise, and splitting it would only make every master noisier |
| Wrong camera or size | Two bodies with the same sensor format would otherwise calibrate each other |
| Bias together with a dark | The dark already carries the offset; subtracting both removes it twice. The bias goes on correcting the flats instead |
A flat, on the other hand, does not have to match the exposure of the lights – a flat is a ratio, and its own exposure time says nothing about them.
Registration without resampling
register -2pass computes the registration data and stops. seqapplyreg would interpolate every pixel – and interpolation correlates neighbouring noise and moves flux inside the aperture. Instead the photometry follows the stars through the registration data: the aperture lands on the star while the pixels stay exactly as the sensor recorded them.
Detrending the airmass ramp without eating the depth
A plain fit through every point absorbs part of the transit whenever the dip correlates with the ramp – the standard case for a target that sinks through the night. So the baseline is first fitted with a one-sided least-trimmed pass, then re-fitted directly on the points outside the transit window.
Measured on synthetic runs with a known 30 mmag-per-airmass ramp: a plain fit recovers the slope 6 to 11 per cent low; this lands within one per cent up to 50 per cent duty cycle, and within three at 75.
Is it real? A floor that was measured
The significance test is two-sided. A real transit returns to the baseline it left; a trend does not. Pooling both sides into one out-of-transit mean loses exactly that difference: on a monotonic ramp with no transit in it – uncorrected extinction, a drifting cloud, focus creep – the pooled contrast reaches +25σ. Comparing each side separately and taking the weaker one, the same data return −10σ and are refused.
The significance is the best of some 40,000 grid nodes, so it is not a Gaussian σ. That is why the floor was not chosen but measured – over 1,200 transit-free noise runs through that same search:
| Floor | False alarm | 4 mmag | 6 mmag | 8 mmag | 12 mmag |
|---|---|---|---|---|---|
| 3.0 σ | 7.67 % | 88 % | 100 % | 100 % | 100 % |
| 3.5 σ | 1.92 % | 70 % | 97 % | 100 % | 100 % |
| 4.0 σ | 0.50 % | 45 % | 93 % | 100 % | 100 % |
| 4.5 σ | 0.25 % | 29 % | 89 % | 100 % | 100 % |
| 5.0 σ | 0.00 % | 15 % | 78 % | 100 % | 100 % |
A 3σ floor lets one run in ten through – where “3σ” is generally read as one in 750. The measured rate is printed next to every result. And a transit clipped by the start or end of the run returns zero significance, not merely less: without baseline on both sides, no method can answer the question.
The aperture is chosen, not assumed
Aperture size is the most consequential number in aperture photometry. Too small loses a seeing-dependent fraction of the star – a systematic that drifts with the night. Too large collects sky and neighbours. So six radii from 0.75 to 2.5 × FWHM are each photometered once, and the one with the least robust scatter wins. Ties are broken by the number of frames measured – an aperture that scores well because it measured fewer frames has won nothing.
Comparison stars
Four filters decide, each against a different failure:
| Dropped when | Why |
|---|---|
| Saturated | A clipped core does not scale with transparency – one saturated comparison star turns every cloud into a false transit |
| SNR below the limit | Every comparison star brings its own photon noise into the ensemble. Below about 20 it adds more scatter than it is worth as a reference |
| Closer than 10 × FWHM | The apertures share sky annulus and stellar wings. The contamination depends on the seeing, so it drifts through the night and looks like a slow trend |
| Not isolated | The same argument, pointed at every neighbour of the comparison star. The radius is Siril’s own geometry, not taste: -autoring puts the outer ring at 6.3 × FWHM, so two annuli stop touching at twice that |
Beyond that, every candidate is photometered against the others and judged on the robust scatter of its own curve. A star that wobbles against its peers writes that wobble, inverted, into the target curve – and nothing else would ever notice. The threshold is a ratio to the ensemble median, not an absolute millimagnitude: a good and a bad night differ by a factor, and a fixed limit would reject everything in one and nothing in the other.
Every rejection appears in the log and the report – and the tally also counts the stars that passed all four filters and simply were not needed. Without that line, “6 chosen, 668 rejected” out of 864 detections reads like a field that barely yielded a comparison star – when in fact it yielded 195, and the best six were taken.
One satellite must not cost the detection
Measured on a real 12 mmag transit at 4 mmag per point: a single 100 mmag outlier took the significance from 12.1σ to 3.2σ – under the detection floor. A measured transit was reported as not claimed.
What is striking is that the parameters barely moved. What broke was the denominator – the scatter behind the significance was an ordinary RMS, and one outlier inflates it. Switching to a MAD recovers 6.9σ, and removing the point recovers the rest. The reference is a nine-point running median – far shorter than any transit, so a smooth multi-point dip passes untouched. No more than 5 per cent of a run is ever removed; above that the outliers are the data, and the run says exactly that.
The output
| File | Contents |
|---|---|
lightcurve.csv | Every point: JD, raw, centred, detrended, error, airmass |
lightcurve.png | The plot |
report.txt | The whole run as text – comparison stars, every rejection and its reason, the method and the result |
AAVSO_exoplanet.txt | In Exoplanet Watch’s format, with T0 ± error, both depth conventions, Rp/R★ ± error and duration in the header |
⚠️ The AAVSO file is refused unless the times are BJD_TDB. The header declares that system; writing JD_UTC underneath would hand a submission an eight-minute error nobody can see. Nothing is sent anywhere – submitting stays your decision.
Taking good data
| Baseline | Start at least one transit duration before ingress and keep running just as long after egress |
| Do not saturate | Neither the target nor the comparison stars. Keep the peak below about half full well |
| Defocus slightly | Counter-intuitive but standard: spreading the star over more pixels averages out flat-field errors and buys saturation headroom. FWHM 4–6 px is a good target |
| Do not dither | The opposite of the stacking advice. Dithering moves the star onto pixels of different sensitivity – noise you do not need when the star is holding still anyway |
| Keep the exposure | Changing it mid-run alters saturation headroom and scintillation statistics at the same time |
Requirements
Siril 1.4+ with Python support, numpy, PyQt6, matplotlib, astropy – the dependencies install themselves on first start. Ten frames is the absolute minimum; a real transit run has hundreds. The airmass detrend needs the latitude and longitude of the site; without them it is skipped, and the report says so.
The script comes with a test suite: tests/test_lightcurve_helpers.py runs under plain python3 with no Siril, and checks over 450 cases against input with a known answer – synthetic transits of stated depths through the full photometry and fit chain, twelve pure-noise runs that must not be claimed, and error bars calibrated against 24 independent synthetic nights.