Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Colourising

A solar capture through a narrow filter is a picture of one colour of light, so the stack is monochrome. The orange of H-alpha, the violet of calcium and the yellow of sodium are choices made when a picture is shared, not something the camera measured. Helia gives each common solar line a recommended look, and applies it only when you ask for colour.

Colour is presentation, not measurement. For a filter capture the stack stays mono. A FITS file is never coloured by a look and its pixels are never touched. (White light with a colour camera is the exception: see True colour.) Colour goes to a 16-bit RGB TIFF and an 8-bit PNG, and every file Helia writes records which look was used (see What the files say). If you need numbers, use the FITS file; if you want to share a picture, use the colour one.

True colour

The looks below are for narrow-band captures, where the camera records one colour of light and the colours are a convention. White light taken with a colour camera is different: the Sun really is several colours at once, and the camera records all of them in its Bayer mosaic. --colour true (API colour: "true", the True colour chip in the Stack step) stacks that.

  • One alignment. The frames are scored, aligned globally and measured at the alignment points on the luminance (the mean of the three planes), once. The red, green and blue planes are then stacked with those same shifts, alignment point field and frame weights, so the three planes see the same sky. It costs three stacks (and three reads of the capture) rather than one.
  • Channel alignment. The air disperses colours, so at a low Sun red and blue sit a fraction of a pixel to either side of green. Each of the stacked red and blue planes is registered to the green by phase correlation and moved by what that finds (--no-channel-align skips it). A shift under 0.05 px is noise and one over 6 px or with a poor lock (confidence under 0.3) is not believed; the history and summary.true_colour.channels say what was applied. The shift is measured on the planes as demosaiced: the Malvar-He-Cutler method borrows green's edges into red and blue, so it hides about a quarter of a displacement (on the synthetic test, 0.45 of 0.6 px), and the planes then agree with each other, which is what the picture needs.
  • White balance (--white-balance). auto, the default, makes the middle of the disc (inside 80% of the radius) neutral: gains for red and blue that bring their means to the green's. off keeps the sensor's own balance, and 1.9,1,1.5 gives the three gains. A raw sensor does not weigh the colours as the eye does, so off is rarely what you want for a picture. A channel that holds far less than the green (blue behind a sodium filter) is held at a gain of 4 with a warning, rather than amplified into noise: true colour is for white light.
  • Scale. All three channels share one scale, chosen so that the 99.99th percentile of the brightest channel of each pixel is 1. Nothing clips, and the balance of the colours is the white balance, not the scale.
  • Edits. Sharpening, levels and the other edits run on each channel, as they do for any colour image, so a limb-darkening flatten also corrects the colour of the limb, which darkens differently in each channel. Areas that were flattened in every frame (burned-in text, exclusions) are made grey, so the balance does not tint them.
  • Files. An RGB TIFF or PNG, or a FITS cube of three planes; see Output formats. A look (--preset colours, --invert, --colour-strength) and --channel do not apply, and asking for them is an error. The luminance is what the alignment, the limb fit, the crop and the summary use.

It needs a raw colour capture (a Bayer mosaic): a mono capture, or one that is already demosaiced, has no colours to stack and is refused before any work is done.

What the real captures say

The four captures available are all ASI482MC (RGGB), and three of them are sodium, where the blue pixels are empty. helia stack --colour true --preset white --keep 12% on them (the gains include the common scale):

CaptureGains (red, green, blue)Channel alignment
Sun_Test.ser (sodium)0.96, 1.02, 4.10 (blue held at the cap)red +0.89, +0.00 px; blue not moved
15_20_57.ser (sodium)1.07, 1.00, 3.99 (blue held)red +1.18, -0.09 px; blue +0.06, -0.01 px
15_32_09.avi (sodium)0.82, 1.31, 5.23 (blue held)red +1.83, -0.25 px; blue +0.40, 0.00 px
14_42_57.avi (broad, low contrast)0.76, 1.09, 0.96red +0.11, +0.10 px; blue -0.17, -0.14 px

The sodium captures show why the cap exists (an uncapped blue gain of 15 to 23 would be all noise) and that true colour is not for them: their red and green planes show different things through a sodium filter, so the red shift of a pixel or two is the planes disagreeing, not dispersion. 14_42_57.avi is the one broad-band capture; its planes agree to under a fifth of a pixel, and the CPU and the GPU (an A100) give the same gains and the same shifts to three decimals (5.4 s and 4.0 s for 630 frames). There is no good white-light capture in the test set, so the colour of a real white-light disc has not been checked. What is checked, with known answers, is a synthetic colour disc: the balance, the dispersion (and that planes of the same scene are found not to move), the crop and the files; and the device against the CPU, with and without alignment points, to 4e-7 in the samples.

The presets

Each preset is a filter, with a recommended way to process its captures and a look. The table is generated from the code that colours the pictures, so it is always what Helia does. The ramp runs from black to white through the colours of the look; the swatch after it is what a mid-grey becomes.

IdFilterWavelengthHueRamp, black to whiteMid-grey becomesBayer channelFrames kept
haH-alpha656.3 nmorange-red #A95A21red (r)25%
cakCa II K393.4 nmviolet #5D3D9Bblue (b)30%
cahCa II H396.8 nmviolet #703096blue (b)30%
mgMg I b517.3 nmgreen #429B42green (g)30%
naNa I D589.0 / 589.6 nmyellow-orange #A06212green (g)12%
he-d3He I D3587.6 nmyellow #9B8617all combined (lum)25%
hbetaH-beta486.1 nmblue-cyan #2474A0blue (b)30%
whiteWhite lightcontinuumwarm neutral #807A70all combined (lum)12%

Why each preset recommends what it does:

  • H-alpha (ha): H-alpha is deep red, so a colour camera records it almost only in the red pixels; the green and blue pixels add noise, not detail.
  • Ca II K (cak): Ca K is at the violet edge of vision and a sensor sees it in its blue pixels. The line is faint, so a few more frames (30%) are kept to average the noise down.
  • Ca II H (cah): Ca H sits 3.4 nm from Ca K, so it takes the same channel and frame share. It is drawn a touch redder so the two can be told apart in a pair.
  • Mg I b (mg): 517.3 nm is green, which is where the green pixels of a Bayer sensor respond. The line is dim, so 30% of the frames are kept.
  • Na I D (na): 589 nm is yellow, which a Bayer sensor splits between its red and green pixels, and the sodium filter passes almost nothing to the blue ones. Averaging all three channels mixes those empty blue pixels into the stack and prints the mosaic as a fine diagonal hatch over the granulation, so the green channel (two pixels in four) is stacked: on the owner's captures it holds 1.4 to 2.1 times the granulation contrast of the luminance. It shows granulation, so only the best 12% of frames are kept and the alignment points are a few granules wide.
  • He I D3 (he-d3): 587.6 nm is yellow-green, shared by the red and green pixels, so the luminance of all three is used. It is drawn yellower than sodium.
  • H-beta (hbeta): 486.1 nm is blue-cyan and a sensor sees it in its blue pixels. It is faint, so 30% of the frames are kept.
  • White light (white): A white-light capture already carries all three colours, so the luminance is stacked, and a slight warm tint stands in for the solar spectrum. It shows granulation, so only the best 12% of frames are kept and the alignment points are a few granules wide.

The numbers behind each colour, for anyone who wants to reproduce it:

IdTone exponentGains (R, G, B)Exponents (R, G, B)
ha0.851.00, 0.85, 0.600.70, 1.50, 2.60
cak0.900.85, 0.78, 1.001.35, 1.90, 0.80
cah0.900.90, 0.70, 1.001.15, 2.10, 0.85
mg0.900.80, 1.00, 0.751.80, 0.80, 1.70
na0.901.00, 0.74, 0.400.75, 1.05, 2.80
he-d30.901.00, 0.92, 0.450.80, 0.90, 2.60
hbeta0.900.55, 0.85, 1.002.20, 1.00, 0.75
white1.001.00, 0.96, 0.881.00, 1.00, 1.00

The recommended sharpening of each filter (see Sharpening and adjustments). Wavelet gains and noise cuts are for layers 1 to 6, finest first; the unsharp radius comes from the image scale, and the deconvolution blur is measured from the disc.

IdWavelet gainsWavelet noise cutsUnsharp amountUnsharp thresholdDeconvolution iterationsTV weightDenoise
ha1.50, 1.45, 1.25, 1.05, 1.00, 1.001.80, 1.00, 0.50, 0.00, 0.00, 0.000.600.003120.0041.5
cak1.30, 1.40, 1.25, 1.05, 1.00, 1.002.50, 1.50, 0.80, 0.30, 0.00, 0.000.450.00480.0052.0
cah1.30, 1.40, 1.25, 1.05, 1.00, 1.002.50, 1.50, 0.80, 0.30, 0.00, 0.000.450.00480.0052.0
mg1.40, 1.40, 1.20, 1.05, 1.00, 1.002.20, 1.30, 0.60, 0.00, 0.00, 0.000.500.004100.0051.8
na1.20, 1.80, 1.50, 1.10, 1.00, 1.002.00, 0.00, 0.00, 0.00, 0.00, 0.000.500.003100.0051.5
he-d31.30, 1.35, 1.20, 1.05, 1.00, 1.002.50, 1.50, 0.80, 0.20, 0.00, 0.000.400.00480.0052.0
hbeta1.30, 1.40, 1.25, 1.05, 1.00, 1.002.50, 1.50, 0.80, 0.30, 0.00, 0.000.450.00480.0052.0
white1.50, 1.80, 1.50, 1.10, 1.00, 1.001.00, 0.00, 0.00, 0.00, 0.00, 0.000.800.002200.0031.0

Why each filter is sharpened the way it is:

  • H-alpha (ha): H-alpha has plenty of signal and its fibrils and spicules sit on layers 2 and 3, so those layers are lifted most; the finest layer gets a high noise threshold.
  • Ca II K (cak): Ca K is faint and noisy, so the gains are gentler, the noise thresholds higher and the deconvolution short: sharpening noise only makes a prettier mess.
  • Ca II H (cah): Ca H is as faint as Ca K and takes the same careful settings.
  • Mg I b (mg): Mg b2 is dim, so the noise thresholds are a little higher than for H-alpha and the deconvolution shorter.
  • Na I D (na): Sodium D is photospheric: granulation is its finest low-contrast detail. Layers 2 and 3 (granule size) are lifted most and carry no noise threshold, because a threshold there removes granulation with the noise; the finest layer keeps its threshold.
  • He I D3 (he-d3): He D3 shows only in faint prominences and flares, so it is sharpened with the most restraint.
  • H-beta (hbeta): H-beta is faint, like the calcium lines, and takes the same gentle settings.
  • White light (white): White light has the most signal and the finest structure (granulation), so it takes the strongest gains and the longest deconvolution. Layers 2 and 3 (granule size) are lifted most and carry no noise threshold, because a threshold there removes granulation with the noise.

The processing recommendations apply to raw colour cameras and to the share of frames kept:

  • Bayer channel. A colour sensor records each wavelength mostly in one kind of pixel, so stacking that plane alone (--channel r for H-alpha) avoids the noise of the other two. A mono camera has no channels and ignores this.
  • Frames kept. Faint lines (calcium, magnesium, H-beta) are noise limited, so 30 per cent of the frames are kept instead of 25 to average more of it away.
  • Alignment point size is not a property of the filter. Helia chooses it from the image size, about a sixteenth of the short side rounded to a multiple of 8 and kept between 32 and 96 pixels.

Every recommendation is only a default. --keep, --channel and --ap-size (and the same controls in the app and the API) override it.

How the colours are made

Colourising is the classic amateur method: levels and gamma on the grey value, then a gain and an exponent for each of red, green and blue. For a mono value v from 0 to 1, a preset with tone exponent g, gains k and exponents e gives, for each channel,

t   = v ^ g                      (after inversion: v becomes 1 - v)
rgb = mix(t, min(1, k * t ^ e), strength)

Each exponent is positive, so every channel rises steadily from black to its gain: a darker input never gets a brighter output. The result is sampled once into a table of 4096 colours, which the CPU and the GPU both look up with the same linear interpolation, so they agree to within a rounding error.

  • Invert turns the picture negative before it is coloured. A disc-inverted H-alpha image shows filaments and prominences clearly, and is common practice. An inverted picture is exactly the normal look read backwards: a pixel of value v gets the colour that the normal look gives to 1 - v.
  • Strength mixes between grey (0 per cent) and the full colour (100 per cent). It is a way to keep a hint of colour, or none.
  • Mono is the preset-free choice: the grey stack as it is, optionally inverted.

Where the conventions come from

There is no formal standard for solar false colour. The project's research notes (State of the art, October 2026, section 6.2) record that amateur practice is greyscale, pseudo-colour, partly inverted and inverted pseudo-colour variants per filter, that H-alpha is commonly rendered red-orange, and that no source for a Ca K false colour was found. Helia therefore follows the colour of the line's wavelength where the eye can see it (H-alpha red, magnesium green, sodium and helium yellow, H-beta blue-cyan), a conventional violet for the two calcium lines at the edge of vision, and a slightly redder violet for Ca H so a pair can be told apart. They are a starting point; change the look in the app if you prefer another.

In the desktop app

After a stack, Helia moves to the Colour step (between Sharpen and Export).

The Colour step with the H-alpha look chosen

  • The cards show each look as a ramp from black to white, with its name and wavelength. Click one and the stack in the viewer changes straight away; it is made on a background thread, so the window stays responsive.
  • Invert and Strength change the preview as you move them.
  • The text under the cards says why the preset recommends what it does.
  • The app remembers the look you last chose for each filter, in its settings file, so the next H-alpha capture starts with the look you used last time.

A capture with the filter in its name starts with that filter's own look, so the usual path needs no choices at all. The Export step says what will be written, and the viewer shows the stack as it will be exported.

On the command line

helia stack ha.ser -o ha.tif --preset ha          # recommended settings and colour
helia stack ha.ser -o ha.png --preset ha --invert # a shareable inverted picture
helia stack Sun_CaK.ser -o cak.tif --colour auto  # colour from the detected filter
helia stack sun.ser -o sun.tif --colour mono      # plain grey, even if detected
helia stack ha.ser -o ha.tif --colour cak         # a different look for the same stack
helia stack ha.ser -o ha.tif --preset ha --colour-strength 0.6
helia presets
  • --preset <id> says which filter the capture was taken with. It applies the recommended channel and frame share and colours a .tif or .png output.
  • --colour <auto|mono|id> chooses only the colour. auto uses the preset you gave or the filter Helia detected (and fails with a clear message if there is none).
  • --invert and --colour-strength adjust the look.
  • Without --preset or --colour, the output is grey, and Helia prints a tip if it detected a filter.
  • For .fits, colour options are noted in the header and the pixels stay unaltered mono.

See the CLI reference for every option.

Exporting a colour table

The look of a filter can be written as a colour table, to colour the same stack in another program so that the picture matches one made here (#23):

helia lut ha -o ha.cube                 # a 1D .cube LUT, 256 entries
helia lut ha -o ha.lut                  # an ImageJ / Fiji .lut
helia lut cak -o cak.csv --size 1024    # value,red,green,blue as text
helia lut ha -o ha-strip.png --invert --strength 0.8
FormatFileRead by
cubea 1D .cube: a title, the domain and size rows of red, green, blueDaVinci Resolve, Adobe tools and others
csvvalue,red,green,blue, one row per entryspreadsheets and scripts
imageja .lut of 768 raw bytes (256 red, 256 green, 256 blue)ImageJ and Fiji
pnga strip image size wide, 32 highanything that loads an image

--format says the format, or the extension of -o does; --size is 2 to 4096 entries (an ImageJ table is always 256); --invert and --strength are the colour options of the stack. The table is the one Helia colours with, resampled by the same linear interpolation, so a 4096-entry table reproduces it exactly and the .cube of a filter applied to its mono stack gives the same picture up to the rounding of the other program. In the app, the Colour step has the same four formats under Colour table, and the API serves GET /api/v1/presets/{id}/lut?format=cube&size=256&invert=false&strength=1. Colour is presentation, not measurement: the table says nothing about the Sun.

In the API

GET /api/v1/presets lists the presets with their recommendations and colour parameters, and POST /api/v1/captures/inspect reports the settings it would use for a capture (recommended). A job takes preset, colour, invert and colour_strength in its config. See The API.

How the filter is recognised

Helia looks for the filter in these places, most trusted first, and stops at the first match:

  1. the file name (without its extension);
  2. the name of the folder the file is in;
  3. the SharpCap CameraSettings.txt next to it: its Filter, Filter Name, Filter Wheel, Target, Description and Notes entries;
  4. the instrument, telescope and observer strings of a SER header;
  5. the name of the folder above.

The text is split into words at punctuation, at a lower-case to upper-case change (SunHa is Sun and Ha) and between letters and digits (cak3934). A word, or two or three words run together (Ca II K is caiik), must then match exactly, ignoring case:

FilterWords
H-alphaha, halpha, h-alpha, hydrogen alpha, 656
Ca II Kcak, ca k, ca ii k, calcium k, 393
Ca II Hcah, ca h, ca ii h, calcium h, 396
Mg I bmg, mgb, mgi, magnesium, 517
Na I Dna, nad, sodium, 589
He I D3d3, hed3, helium, 587
H-betahb, hbeta, h-beta, 486
White lightwhite, whitelight, wl, continuum, cont

A number such as 656 counts only on its own, so a date like 2026-05-17 is not mistaken for a wavelength. A word inside a longer word never counts: Hampshire is not H-alpha and Sha_nal is nothing. If the filter is not recognised, the app asks once with a chooser, and the command line uses plain defaults and says filter not recognised.

What the files say

Every file records the look it was written with, in words, so a coloured picture cannot be mistaken for a measurement later:

FileWhere the note is
TIFFthe ImageDescription tag
PNGthe Description text chunk (and Software: Helia)
FITSHISTORY cards in the header

The note reads like Colour is presentation, not measurement: preset ha (H-alpha 656.3 nm, orange-red), invert off, strength 1.00. The FITS pixels are written exactly as they would be without it; only the header differs.

The same places also record how the capture was read, whether or not a colour look was chosen, so a stack can be traced back later. These lines are always written:

Capture read as: Bayer RGGB (detection), 16-bit (header), little-endian (detection)
SER header: ColorID 0 (MONO), 16-bit, big-endian
Bayer detection: 2x2 position means 79.6% / 77.6% / 77.4% / 0.3% of full scale, spread 135%, 5 frames
Overrides: --bayer none, --bit-depth 16

The first says which Bayer pattern was used (or none (mono)), the bit depth and (for 16-bit data) the byte order, each with its source (header, sidecar, detection or override), and adds rows flipped when --flip-rows was given. The second is the SER header's own claim, the third appears only when a mosaic was detected, and the fourth only when you overrode something. When the stack was calibrated there is one more line for each master (Calibration: dark subtracted (master dark of 64 frame(s), sigma-clip 3, exposure 0.6400 ms, from darks.ser, fp:...), the same for the flat and for the bad pixels); see Calibration. TIFF joins the lines with ; , PNG puts them in the text chunk, and FITS has one HISTORY card (or more, when a line is long) for each.

What it does not do

  • It colours the stacked image only. Prominence and disc compositing into one high-dynamic-range picture is separate work, tracked in issue #23; colourising here is the colour part of that issue.
  • There are no user-defined palettes or exported lookup tables yet.
  • Sharpening comes before colouring, and is its own step (Sharpening and adjustments): the colour is applied to the sharpened mono image, never the other way round.