Starnett++ and Siril
This tutorial aims to present a python script allowing to simply interface Starnet++ and Siril. This tool meets the needs of users to be able to manipulate images from Starnet++ with Siril and especially Pixel Math.
Why this script? #
Images from Siril pre-processing are in standard FITS/32bit format. However, the images usable by Starnet++ must be in TIFF/16bit format. They must therefore be converted. Similarly, Starnet++ output images are in TIFF/16bit. If you want to benefit from Siril’s calculation precision, you have to convert them to FITS/32bit. This format is also the only one supported by Siril’s PixelMath tool.
Prerequisites #
The goal is to use here a script written in python (that everyone can adapt), and based on the Siril ecosystem. For this, you will first need:
- install pysiril, see https://siril.org/tutorials/pysiril/
- install astropy, see https://www.astropy.org/
- Install PIL
- Download and Unzip the Command Line Tool version of Starnet++ (https://www.starnetastro.com/download/) in a known directory.
The python script must then be placed in this directory, just next to the executable starnet++.exe
.

The script place
What does this script? #
- It transforms the input files in FITS/32 into TIFF/16bit file(s)
- It launches Starnet++
- It re-transforms the result file into FITS/32
- It updates the
FILTER
field (FITS header) of the generated files to make them compatible with PixelMath
The file(s) only need to be “dragged” onto the Python script as shown in the video.
The input files #
- They must be of the FIT/FITS type.
- The best is to use files whose histogram has already been stretched.
- It can be a mono file (R, G or B) from a
split
command or Ha and/or OIII files from anextract
command. - It can be an RGB file (3 layers therefore).

Example of input files set
The output files #
At the output of the script, 2 possibilities:
- If the input file was mono, only the
myfile_s.fits
file is generated._s
for “starless” … - If the input file was in RGB, in addition to the
myfile_s.fits
file, themyfile_stars.fits
file (the star mask) is also generated.

Example of the corresponding output files
What about PixelMath? #
The PixelMath tool recently introduced in Siril makes it possible to manage the images to be handled thanks to a sorting facilitated by the FILTER
field of the FITS header.
This python script remains in this perspective and updates this FILTER
field.
Thereby:
- the starless file, whose name is of the type
myfile_s.fits
, also have a field of the typeFILTER = CLS_sls
(if CLS was the original value…). - the star mask, whose name is of type
myfile_stars.fits
, also have a field of typeFILTER = CLS_str
(if CLS was the original value…).
Let’s see an example #
Here it is a mix of mono images (R.fits
, G.fits
, B.fits
) and an RGB image (result2-CLS.fits
) made with a CLS filter (and therefore FILTER=CLS
).
These 4 images are dragged simultaneously onto the script, and after several minutes of calculation, we see the resulting files.
Then, by opening Siril and PixelMath, one can realize the efficiency of sorting images according to the updated FILTER
values.
This effect is seen on mono images, then on RGB images.
The python script #
Just copy the code below and save to a file named AutoStarnetFITS.py
(for example).
import subprocess
import sys
import os
from PIL import Image
from pysiril.siril import Siril
from pysiril.addons import Addons
from pysiril.wrapper import Wrapper
import pathlib
from astropy.io import fits
def Run(*args, trailer= '_s', stride= '256', keep_open=True ):
#preparing pysiril
workdir = os.getcwd()
print('Starting PySiril')
app = Siril()
AO = Addons(app)
cmd = Wrapper(app)
app.tr.Configure(False,False,True,True)
app.MuteSiril(True)
app.Open()
print('Starting Siril')
for file_in in args:
print('\nFile to process :', str(file_in))
#Get attributes (name and extension) of the input file
filename, inputfile_extension=os.path.splitext(file_in)
if inputfile_extension!='.fits' and inputfile_extension!='.fit':
print ('ABORTED: Not a FIT/FITS file')
return
#Get the FILTER value of the FITS header
hdul = fits.open(file_in, mode='update')
try:
fltr0=hdul[0].header['FILTER']
except Exception as e :
print("\n******* " + str(e) + "in "+ str(file_in))
print("******* Creating the missing field... " + "\n" )
hdr= hdul[0].header
hdr.insert('DATE', ('FILTER', ' '))
hdul.flush()
fltr0=hdul[0].header['FILTER']
print("FILTER = " + str(fltr0) + "\n" )
hdul.close(output_verify='fix')
#Set pysiril
app.Execute('setext {:s}'.format(inputfile_extension))
app.Execute('cd {:s}'.format(workdir))
# Open the original input 32b FITS file
# convert it into 16b and save as TIF file with the same name
cmd.load(filename)
cmd.set16bits
cmd.savetif(filename)
#Define the 16b version of the initial file, used as input for Starnet++
filename_tif = filename + '.tif'
#Get attributes (name and extension) of the last file
fileroot, file_extension=os.path.splitext(filename_tif)
#Define the output file of Starnet++
outputfile_extension=".tif"
outputfilename=filename+trailer+outputfile_extension
#String Compatibility test
if ' ' in filename_tif:
print ('FAILURE: No space allowed in file name or file path')
return
#Define image mode
im = Image.open(filename_tif)
imode=im.mode
im.close()
need_stars = True
if imode=='I;16':
need_stars = False
##Few results of "mode" method according to file format input
## color Mono
##fits 32 F F
##fits 16 F F
##fits 16u F F
##fits 8 F F
##Tif 32 / F
##Tif 16 RGB I;16 (the only file format unsable by Starnet)
##Tif 8 RGB L
#Compatibility test
if ((imode=='I;16' or imode=='RGB') and file_extension=='.tif') :
#set argument for Starnet++
args= "starnet++.exe " + filename_tif +" "+ outputfilename + " " + stride
print ('Starnet++ is running... ')
subprocess.call(args, shell=True)
else:
print ('Not a TIF/16bit file')
return
#Convert the result starless file into a 32b version
starless_filename = fileroot + '_s'
cmd.load(starless_filename) #Load bla_s.tif
cmd.set32bits
cmd.fmul('1.0')
cmd.save(starless_filename) #Save bla_s.fits
#Creation of the Stars.fits
if need_stars == True:
folder,_=os.path.split(starless_filename)
#stars=folder+"\\" + fileroot + "_stars.fits"
stars=filename+"_stars"
cmd.load(file_in) #Load bla.FITS, stars+neb
cmd.isub(starless_filename) #substract starless version
cmd.save(stars) #save stars.FITS
#Deletion of TIF temp files
tokill=starless_filename + '.tif'
os.remove(tokill) #Remove bla_s.tif
os.remove(filename_tif) #Remove bla.tif
########################################
########################################
#Set the FILTER value in the FITS header
# At first, for the Starless file
sl_name=starless_filename+'.fits'
#print ('starless file2: ', sl_name)
hdul = fits.open(sl_name, mode='update')
try:
fltr=hdul[0].header['FILTER']
except Exception as e :
print("******* " + str(e) + "in "+ str(sl_name))
print("******* " + " Creating the missing field... ")
hdr= hdul[0].header
hdr.insert('DATE', ('FILTER', ' '))
hdul.flush()
fltr=fltr0
hdr = hdul[0].header
newfltr= str(fltr)+'_sls'
hdr['FILTER'] = newfltr
hdul.close(output_verify='fix')
########################################
# ...and then for the Stars file
if need_stars == True:
stars=stars+'.fits'
#print ('stars file2: ', stars)
hdul = fits.open(stars, mode='update')
try:
fltr=hdul[0].header['FILTER']
except Exception as e :
print("******* " + str(e) + "in "+ str(stars))
print("******* " + " Creating the missing field... ")
hdr= hdul[0].header
hdr.insert('DATE', ('FILTER', ' '))
hdul.flush()
fltr=fltr0
hdr = hdul[0].header
newfltr= str(fltr)+'_str'
hdr['FILTER'] = newfltr
hdul.close(output_verify='fix')
########################################
app.Close()
del app
print ('Stop')
if __name__ == "__main__":
Run(*sys.argv[1:])
It’s up to you now!!!