How many short exposures do you have to stack before a faint nebula stands out in every single pixel? I stacked 194 twenty-second exposures of the North America Nebula (NGC 7000) from a Seestar S30 Pro. The graininess fell closely along the textbook square-root rule (measured), but at 194 exposures the nebula's typical glow was only 2.4 times the graininess in each pixel (measured), short of the 3 times that counts as a clear detection. Getting to 3 would take about 302 exposures, roughly 100 minutes of exposure time with 20-second frames (inferred from the measured trend).
Registered mean and sigma-clipped stacks of 194 IR-cut 20 s Seestar S30 Pro subs of NGC 7000, red pixels only. Per-pixel noise, measured with a null test (the difference of two interleaved half-stacks), follows σ1/√N from N = 2 to 194 (measured), at 0.938 to 0.961 of the prediction from a 125.8 ADU single-sub noise fixed in advance. Nebula S/N per pixel at the field's median Hα is 2.40 at N = 194 (measured), so S/N = 3 needs N ≈ 302 (inferred, √N extrapolation; about 270 to 340 within the calibration's uncertainty). A second unit through the LP filter (864 of 919 10 s subs) follows the same law to N = 864, but its stars grow 22 % in the stack and fail the 15 % pass mark set in advance. The stack that passes the noise test is an unscaled version added after the first look; the version written into the plan in advance, scaled by star flux, failed it.
A smart telescope like the Seestar takes many short exposures, called subs, and adds them up into a stack. Each sub is grainy. That graininess is noise: random pixel-to-pixel jitter on top of the real light. Stacking averages the jitter down, so faint things that are buried in one sub start to show.
The usual rule of thumb is that noise falls with the square root of the number of subs: four times as many subs gives half the noise, a hundred times as many gives a tenth. I wanted to check that rule on real Seestar frames, and then answer a practical question with a number: how many 20-second subs does it take before the North America Nebula's typical glow stands clearly above the noise in each pixel?
I measure brightness in ADU (analog-to-digital units), the raw numbers the camera writes for each pixel. I used only the camera's red pixels: the sensor has a grid of red, green and blue pixels, and the nebula's glowing hydrogen shines in deep red light.
Two questions, written down with their pass marks and predictions before any pixel was read (stated). First, does a registered mean or sigma-clipped stack of N consecutive subs reach per-pixel noise σ1/√N, with noise that stays close to white (uncorrelated between neighboring pixels)? Second, at what N does the nebula, at the median Hα surface brightness of the frame, reach S/N ≥ 3 per pixel?
Data: 199 subs of 20 s, IR-cut filter, gain 200, from one Seestar S30 Pro on the night of 13 to 14 June 2026, 22:57 to 00:30 local (a 92-minute session). Everything is measured on the red plane, the [0::2, 1::2] pixels of the GRBG Bayer matrix (the sensor's mosaic of color filters), 1080 × 1920 pixels, in ADU as read with no bias subtracted. The predicted single-sub noise, 125.8 ADU, came from an earlier pair-difference measurement on the same frames (stated). The nebula's brightness comes from a published all-sky Hα map (Finkbeiner 2003), tied to my ADU by a regression of 64-pixel blocks against the map with a smooth sky term.
From 2 to 194 subs the noise per pixel stays within 1.2 % of one square-root curve (measured; the curve's one-sub value was fitted after the fact), and on a second telescope within 8 % up to 864 subs (measured).
The square-root ruleThe nebula's median glow sits 2.4 times above the noise in each pixel (measured). Three times needs about 302 subs (inferred); averaging 2 × 2 pixels gets 4.8 now (measured).
When the nebula showsStars came out 5 % wider than in a typical single sub with 20 s IR-cut subs (measured), and 22 % wider on the sharper 10 s LP set (measured).
SharpnessThe blue points in the chart sit right on the straight line of the square-root rule (measured). That line starts from about 119 ADU of noise per red pixel for one 20-second sub, a value fitted to the stacked points. Stack 4 and it drops to about 59; stack 16 and it is 30; stack 194 and it is 8.5 (all measured). Doubling the number of subs does not halve the noise; it divides it by about 1.4. To halve it you need four times the subs.
How do you measure noise when the picture is full of stars and nebula? I split the subs into two piles, every other sub, stacked each pile, and subtracted one stack from the other. Everything real is the same in both piles and cancels. What is left is the noise of the two halves, which tells you the noise of the full stack.
The purple points are the same subs lined up a different way. Lining up means moving each sub by fractions of a pixel and turning it slightly, because the sky drifts and, on an alt-az mount like the Seestar (one that moves up-down and left-right instead of turning with the sky), slowly rotates in the frame. The blue stack simply takes the nearest pixel. The purple stack blends neighboring pixels smoothly (cubic spline resampling). Blending makes each pixel look 14 % less noisy (measured), but only because neighboring pixels now share their noise, so it does not make faint things easier to detect. I count detection from the blue numbers.
What I predicted in advance was a little higher, 125.8 ADU per sub (the dotted line). The real stack ran 4 to 6 % under that (measured), because my stacker now repairs warm pixels, pixels that read a bit high in every frame, and the earlier measurement had included them. This sensor was at 29 °C (stated, from the file headers), and 1.5 % of its red pixels were warm (measured).
Noise is the robust standard deviation of D = (A − B)/2 per red pixel, where A and B are the stacks of the even- and odd-numbered subs in time order; for equal halves Var(D) = σstack2. Stars (detections plus Gaia G < 14, grown) and a 32-pixel edge are masked. The nearest-pixel, unscaled stack gives 85.5, 59.5, 42.2, 30.0, 21.2, 14.9, 10.5 and 8.48 ADU at N = 2, 4, 8, 16, 32, 64, 128 and 194 (measured; 8.57 ADU for the sigma-clipped mean at 194). σ√N stays between 118.1 and 120.9 ADU across that range; a single σ1 = 119.4 ADU, fitted to these points after the fact, describes all of them to 1.2 %. Against the 125.8 ADU fixed in advance, the ratio is 0.938 to 0.961 (measured), inside the ±15 % pass mark. The gap is the warm pixels: a self-dark (each frame minus the registered stack mapped back, medianed over frames) flags 30,474 red pixels (1.47 %), median amplitude 291 ADU, 2.3 σ per sub, positive in 99.6 % (measured), and they are replaced by the median of their 8 neighbors before resampling.
Correlation: binning the null into 2, 8 and 32 pixel blocks gives an excess over the white-noise expectation of 1.00, 1.00 and 1.19 for the nearest-pixel clipped stack at N = 194, and 1.11, 1.15 and 1.29 for the cubic-spline stack (measured; a synthetic test before the run predicted 1.15 at 32 px). The spline stack's per-pixel σ is 0.86 of the nearest-pixel value (synthetic prediction 0.876), so its per-pixel S/N would be inflated by about 14 % and is not used for detection.
Caveats. The interleaved null cancels anything common to both halves. A sensor fixed-pattern-only stack (the self-dark's high-pass, mapped through every frame's transform) reads 0.34 to 0.36 of the null σ at every N, an upper bound that adds about 6 % to the true per-pixel noise (measured, after the fact). An early-against-late split instead of odd-against-even raises per-pixel σ by 3 to 5 % and the 32-pixel excess to 1.8 to 2.3 (measured, after the fact): slow sky changes the interleaved null cannot see. Taken together, the true per-pixel noise may be up to about 6 % above the null's value, which would raise N(S/N = 3) by up to about 12 % (inferred).
Signal-to-noise (S/N) is the brightness of what you want to see divided by the noise. At 3, a pixel's glow is very unlikely to be a chance fluke; astronomers write that as 3 σ (sigma, the size of the noise). In a single 20-second sub, the nebula's typical glow is only about 0.17 of the noise (computed from measured values): completely invisible pixel by pixel. With 194 subs it reached 2.40 (measured).
Following the square-root rule, 3 needs about 302 subs, about 100 minutes of 20-second exposures (inferred). Treat that as roughly 270 to 340, because turning the hydrogen map into camera units carries about 5 % uncertainty (inferred), and a faint pattern in the sensor that my noise measurement can't see could push it up by about a tenth more (inferred, see What went wrong). Before running anything I had predicted 2.4 and 305 subs, so this came out as expected. Brighter parts of the nebula get there sooner: at 194 subs, 46 % of the area that all 194 subs cover was already above 3 (measured). And if you average each 2 × 2 block of pixels into one (binning), the median reaches 4.8 now (measured), at the cost of resolution.
The “typical glow” here is the middle value over my frame of a professional all-sky map of hydrogen light, converted into the Seestar's units. Your sky, filter, target and exposure will shift these numbers, but the square-root scaling held on both telescopes I tried (measured).
Signal: S = k · Hαmedian, with Hαmedian = 149.8 R (rayleighs, the map's unit of line surface brightness) over the frame (Finkbeiner 2003 map, resampled onto the red plane) and k = 0.137 ADU R−1 regressed on the unscaled spline stack itself (bootstrap 68 % range 0.130 to 0.145), so S = 20.5 ADU (measured; 20.6 ADU with k = 0.1374 from the unscaled nearest-pixel stack, which the charts use). The same regression on the single reference sub gives 0.1444, reproducing the earlier value exactly (a known-answer check fixed in advance), and raw k is 0.135 to 0.145 on individual subs (measured). Noise: the white-equivalent per-pixel σ, meaning the per-pixel noise with no correlation between neighbors (what sums over large boxes see), taken from the nearest-pixel clipped stack: 8.57 ADU. S/N = 2.40 at N = 194 (measured; all four stack versions give 2.40 to 2.43 when k is taken from the same stack). N(S/N = 3) = 194 × (3/2.403)2 ≈ 302, from the nearest-pixel stack's S/N (inferred, √N extrapolation; the prediction fixed in advance was (3 × 125.8/21.6)2 = 305). Single sub: 20.5/119.4 ≈ 0.17.
At N = 194 the per-pixel 3 σ threshold is Hα ≥ 187 R (predicted 186), met by 45.6 % of the area that all 194 subs cover (predicted 35 to 40 %). Binned 2 × 2, the white-equivalent S/N at the median is 4.8 (measured). The S/N is model-scaled: it assumes the map's median Hα times k is the nebula's true signal in these pixels, so it inherits the map's resolution and any error in k (about ±5 %, which moves N(3σ) to about 270 to 340; inferred, N ∝ k−2).
Stacking has a price: stars get a little wider, because the subs never line up perfectly and lining them up means reshuffling pixels. I measure star size as FWHM, the full width at half maximum: how wide a star's blob is at half its peak brightness, in pixels.
On the 20-second subs, the smooth-resampled stack's stars were 5 % wider than in a typical single sub (measured), well inside the 15 % I allowed in advance. Lining up was good: a star's position wandered by only 0.19 pixel from sub to sub after alignment (measured), and resampling kept the typical star's brightness to within 0.03 % (measured, median over stars).
A cheaper way of lining up, shifting each 240-pixel tile of the image by a whole number of pixels, failed badly: over the 92 minutes the field turned almost 10 degrees (measured), so stars could move by up to 23 pixels within a single tile (inferred from the rotation), and the tile stack kept under 1 % of a star's light inside the usual measuring circle (measured).
FWHM from a pixel-integrated Gaussian fit to about 400 bright, unsaturated Gaia stars (32 for the tile stack, where most stars were too distorted to fit). Single subs: median 1.497 px (range 1.26 to 1.77); the reference sub had the best seeing of the night, 1.21 px. Stacks at N = 194: cubic spline 1.571 px (1.049×, pass), nearest pixel 1.593 px (1.064×), per-tile integer shift 1.831 px (1.223×, fail) (all measured; predicted +5 to 12 % for spline, +10 to 18 % for nearest pixel).
Registration: star-offset voting for the coarse step, KD-tree matching (median 1,058 stars per sub) and a cubic 2-D polynomial transform with 3 σ clipping; residual 0.293 px rms (affine only: 0.300, so distortion is negligible here). Gaia stars detected in ≥ 80 % of subs scatter by 0.191 px median (p90 0.241, 1,064 stars) around their own mean after mapping back (pass mark 0.3 px; predicted 0.05 to 0.12, too optimistic). Aperture flux of a resampled sub against its raw self: 0.9997 (68 % range 0.993 to 1.006) for spline, 0.9991 for nearest pixel; the spline loses 7 % of the peak of an undersampled core (predicted 5 to 15 %). Field rotation −0.4 to −9.8 degrees, frame-center wander up to about 50 px; 83 % of the grid is covered by all 194 subs. Saturated cores overshoot to 73.6k ADU in the spline stack and leave a one-pixel ring, so saturated stars should be masked in any fit.
To see whether the rule holds on other equipment, I ran the same stacker on a second S30 Pro: 919 ten-second subs of the same nebula through its LP filter (a dual-band light-pollution filter that passes only narrow slices of light around the hydrogen and oxygen glow) over four and a half hours on 19 to 20 September 2026. I kept 864; most of the 55 I dropped were taken through cloud (measured).
Switch the chart to the LP set: the noise again follows the square-root rule, from 2 subs all the way to 864, running 2 to 8 % above it (measured). Each 10-second LP sub had only about 38 ADU of noise per red pixel (measured), far less than I predicted (85 to 125). The filter blocks most of the sky glow, so there is less light, and less noise from it, in each sub (inferred). Don't read 38 against 119 as a filter test, though: the telescope, exposure, sky and night all differ.
Here the stars paid more: they came out 22 % wider than in a typical single sub (measured), failing the 15 % I set in advance. These subs were sharper to begin with (1.1 pixels), and a sharp star loses more to reshuffling pixels (inferred). The target also passed close overhead, so the field turned 133 degrees during the night (measured), and stars near the frame edges smeared even within a single 10-second sub (measured).
The Seestar app had made its own stack of this night, from 919 subs (stated, in its file header), where mine used 864. I compared the two like for like, on the red pixels. Its stars have the same brightness scale as mine, to 0.3 % (measured). The difference between the two stacks is smaller than the noise in mine, so I can't say which is less noisy (measured): that would need the app to stack two separate halves. Its stars are 20 to 26 % wider than in my stack (measured, on 519 stars). I had expected 5 to 25 % wider, from its color interpolation step (debayering, which builds a full-color image from the sensor's color grid) plus its own resampling (inferred), so it came out at the top of that range. Bear in mind the app gives you a finished color image in the field, which my red-only test stacker does not.
Passing cloud in the second half of the night dimmed the stars, at worst to 13 % of the first sub, and the nebula faded with them: where the stars fell to a quarter or a third, the nebula's contrast fell to about half (measured). A plain average gives a cloudy sub the same say as a clear one, so weighting subs by how clear they were is the obvious next step (not tested here).
Second unit, LP filter, 10 s, gain 200, sensor 18 to 20 °C, 919 subs in one session, 21:32 to 01:59 local (stated, headers). The pipeline is the same with these changes, written before the stack was built: the coarse registration step was made rotation-aware (prediction through the last good transform plus a widening ±4 degree search), because the field rotated 0 to −132.6 degrees as the target passed near the zenith; only additive second-degree polynomial sky matching (no star-flux scale); two of the earlier rejection rules (fewer than 150 matched stars, transparency under 0.80) were not applied, and the transform-residual rule was made relative. 864 subs used; 55 rejected: 42 registration failures in cloud, 11 elongation, 3 transform residual (one overlapping).
σ1 = 37.7 ADU from 10 registered consecutive pairs (pairs 33.5 to 40.9; prediction 85 to 125 ADU, wrong). Nearest-pixel null: 27.3, 19.3, 9.79, 5.01, 2.55 and 1.31 ADU at N = 2, 4, 16, 64, 256 and 864, that is 1.023, 1.038, 1.063 and 1.079 of σ1/√N at N = 4 to 256 (pass mark ±15 %, pass) and 1.017 at 864 (measured). The N = 864 value covers only the 7.9 % of the grid that every sub overlaps. 32-pixel binned excess 0.99 to 1.23 at N ≥ 16 (pass mark 1.5, pass). Centroid scatter 0.242 px on the 52 stars that stay in ≥ 80 % of subs (pass). Warm pixels 0.27 %, and no sensor pattern above noise at this temperature.
FWHM: single-sub median 1.115 px; spline clipped stack 1.357 px (1.217×, fail); nearest-pixel mean 1.429 px (1.281×). By radius, after the fact: single subs 1.09, 1.07 and 1.17 px and the stack 1.26, 1.24 and 1.44 px for r < 250, 250 to 450 and > 450 px, so about +16 % everywhere plus extra at the edge, where rotation during each exposure already smears the subs.
Comparison with the app's own 919-sub stack (debayered 16-bit RGB; the R plane sampled at R-pixel positions): same grid to 0.01 degree and scale 1.00001; 4,602 stars matched at 0.16 px rms; flux ratio 1.003 (68 % range 0.96 to 1.07), so the app's stack is a mean in the same raw ADU. A model-free blank-sky estimator gives 2.97 ADU per pixel for the app and 3.56 for mine (0.84×; predicted 10 to 35 % lower), but on my stack that estimator reads 3.56 where the half-null reads 1.14, so it is dominated by sky structure and cannot rank the two. After the fact, the app's stack minus mine (mine resampled onto its grid) has 0.82 ADU per pixel on blank sky, below my 1.14 ADU null, plus a roughly 0.5 ADU component flat from 4 to 32 px (different background handling). FWHM on 519 isolated matched stars: mine 1.40 px, the app's 1.68 px sampled at R positions and 1.77 px from the full-resolution image halved, that is 20 to 26 % broader (predicted 5 to 25 %), at every radius.
Clouds, after the fact: the star-flux ratio against the first sub falls to 0.5 to 0.7 in the second half with dips to 0.13, and the diffuse contrast (bright minus dark 32-pixel blocks) tracks it (correlation 0.84; 21 ADU clear, 11 to 13 ADU at ratios 0.26 to 0.30). An early/late split null reads 1.63 ADU against 1.31 interleaved, with more than 10× excess at 32 px.
The version of the stack that passes the noise test is not the one I planned. My plan scaled every sub to match the star brightness of the first sub. That first sub turned out to be odd: the stars in every other sub were 17 to 35 % brighter than in it, while the nebula was just as bright in all of them, and its sky was the brightest of the night (measured). A thin haze or dew can do that, spreading starlight out into a wide glow without dimming smooth light (inferred). Scaling everything to that sub shrank the noise artificially and cut the nebula by about a quarter, so the noise test I wrote in advance failed for that version (measured). The version that passes, with no scaling, just lined up and averaged, is one I added after looking at the data. Its numbers are on this page, labeled as decided after the fact.
Other things stacking did not fix:
None of these overturn the main result (inferred): the noise falls as the square root of the number of subs, and the nebula needs about 300 of these subs to show per pixel. The hidden sensor pattern could push that count up by about a tenth.
The stack planned in advance applied a multiplicative scale si = reference/frame star flux (0.74 to 0.86) to each sub. Every other sub's star-aperture flux is 1.17 to 1.35 times the reference's, while the diffuse emission is not (raw k is 0.135 to 0.145 on every sub; measured), consistent with a scattering haze that moves core flux into a wide halo but conserves extended surface brightness, and with its 30 ADU brighter sky (inferred). Against the pass marks fixed in advance, the scaled stack:
The unscaled stacks, added after the first look, fix both: the nearest-pixel one passes the noise law (0.945, 0.955 and 0.950), and both give k = 0.137 (0.130 to 0.145). Their S/N, 2.40, matches the scaled stacks' once k is taken from the same stack (2.40 to 2.43), so the detectability answer does not depend on the choice. The scaled spline stack's own noise checks also failed: per pixel 0.753 / 0.691 / 0.662 (predicted low, from the scale and the interpolation), and white-equivalent (32-pixel block σ × 32) 1.354 / 0.952 / 0.826, failing at N = 4 and 64. Failures independent of the scaling:
Two further changes from the written plan: the FFT cross-correlation for the coarse shift locked onto the sensor's fixed pattern at (0, 0) and was replaced by star-offset voting; and the warm-pixel map built from the temporal median was rebuilt from a registered self-dark after I suspected it of flagging stars (it was right: 98.4 % the same pixels).
Before reading any pixel, I wrote down how the stack would be built, what would count as passing, and what I expected. The main expectations held: the square-root rule (pass), the nebula falling short at 194 subs (I predicted 2.4 and about 305 subs; I measured 2.40 and 302), and the star-size limit on the 20-second set (pass). The misses are listed above: the star-brightness scaling (which sank the planned noise checks), the patchy sky, the very small stacks, and the LP set's star size. My guess for the noise in a 10-second LP sub was also far off (85 to 125 predicted, 38 measured).
| Check | Fixed in advance | Measured | Result |
|---|---|---|---|
| Noise law, scaled stack, N = 4 / 16 / 64 | within 15 % of 125.8/√N | 0.824 / 0.787 / 0.761 | fail |
| Noise law, unscaled stack (added after looking) | same | 0.945 / 0.955 / 0.950 | pass |
| Noise law, scaled spline stack, white-equivalent | within 15 % | 1.354 / 0.952 / 0.826 | fail (4, 64) |
| Clipped against plain mean noise | ≤ 1.05 | 1.000 to 1.010 | pass |
| 32 px binned excess, N = 4 | ≤ 1.5 | 1.59 to 1.80 | fail |
| 32 px binned excess, N = 16 and 64 | ≤ 1.5 | 1.02 to 1.43 | pass |
| Star residuals in the null at N = 194, spline | ≤ 0.05 px and ≤ 1 % | 0.015 px, 0.64 % | pass |
| Star position scatter across subs | ≤ 0.3 px (predicted 0.05 to 0.12) | 0.191 px | pass |
| Flux kept by resampling | within 1 % | 0.9997 / 0.9991 | pass |
| Stack FWHM over single-sub median, spline | ≤ 1.15 (predicted 1.05 to 1.12) | 1.049 | pass |
| Nebula S/N per pixel at N = 194 | ≥ 3 (predicted 2.4, N = 305) | 2.40 (so N ≈ 302) | fail, as predicted |
| k on the single reference sub (known answer) | 0.14444 reproduced | 0.14444 | pass |
| k on the stack | 0.135 to 0.153 | scaled 0.107; unscaled 0.137 | fail; unscaled agrees |
| Sky structure beyond a 2nd-degree sky fit | ≤ 3 ADU per block | 8.1 ADU | fail |
| LP single-sub noise | predicted 85 to 125 ADU (no pass mark) | 37.7 ADU | prediction wrong |
| LP noise law, N = 4 to 256 | within 15 % | 1.023 to 1.079 | pass |
| LP 32 px excess, N ≥ 16 | ≤ 1.5 | 0.99 to 1.23 | pass |
| LP star position scatter | ≤ 0.3 px | 0.242 px | pass |
| LP stack FWHM over single-sub median | ≤ 1.15 | 1.217 | fail |
| App's stack FWHM over mine | predicted 1.05 to 1.25 | 1.20 to 1.26 | 1.20 inside, 1.26 just above |
The interpolation factors (per-pixel σ ×0.876 and a 1.15 excess at 32 px for cubic spline) were predicted from a synthetic white-noise test before any data were read (stated). σ1 = 119.4 ADU and every after-the-fact split, pattern and radius check above are measured but were not part of the written plan.
Everything here was done with a stacker I wrote for this test in Python, on the red pixels only, not with the Seestar app or any finished product. Each sub was cleaned of warm pixels, lined up on its stars, and averaged, with a gentle outlier rejection (sigma clipping) that ignored values far from the rest. The noise numbers behind every chart are under its “Show the numbers” link. The raw subs aren't posted yet.
Pipeline, in order: red plane as float; warm-pixel repair from a registered self-dark; per-frame source extraction (sep, 5 σ, windowed centroids); coarse offset by star-pair voting, KD-tree matching and a 20-parameter cubic polynomial transform with 3 σ clipping; frame rejection on matched count, transform residual, half-light radius, elongation and transparency (5 of 199 rejected, all on star shape); resampling onto the first sub's grid by cubic B-spline, nearest pixel or per-tile integer shift; additive second-degree polynomial sky matching to the reference from star-masked 64-pixel block medians (plus the star-flux scale in the version planned in advance); mean and sigma-clipped mean (center the per-pixel median, scale the larger of 1.4826 MAD and the per-frame σ, κ = 3, 2 iterations, only where at least 5 subs overlap). Clipping removed 0.10 % (spline) to 0.32 % (nearest pixel) of values. Libraries: sep, SciPy, photutils, Astropy. Hα map: Finkbeiner (2003), ApJS 146, 407. Star positions: Gaia DR3.