Automatiser avec pySiril
pySiril est un package Python étendant les capacités de script, présent nativement dans Siril. Il est destiné aux utilisateurs déjà familiarisés avec les scripts à la recherche d’une alternative à l’utilisation de shell complexes ou bat (sous Windows).
Avec pySiril, vous pouvez:
- écrire des conditions et des boucles (
if
,else
,for
…) - écrire facilement vos propres fonctions
- récupérer les valeurs renvoyées par certaines des fonctions Siril telles que
stat
,bg
,cdg
,bgnoise
…
Cette bibliothèque fonctionne pour les 3 principaux OS, Linux, Windows et MacOS.
Installation #
-
Téléchargez la dernière version à partir de https://gitlab.com/free-astro/pysiril/-/releases
-
Désinstallez les précédentes versions avec :
python -m pip uninstall pysiril
- Install the new whl with:
python -m pip install pysiril-0.0.7-py3-none-any.whl
Vous pouvez également créer le package à partir des dernières sources disponibles sur https://gitlab.com/free-astro/pysiril
Modules #
pySiril contient 3 modules:
- Siril: le module principal qui va démarrer Siril, démarrer les tubes nommés.
- Wrapper: le wrapper de toutes les commandes
que vous saisiriez dans la ligne de commande de Siril ou dans un script (fichier
*.ssf
) - Addons: une classe pratique remplie d’outils pratiques pour manipuler des fichiers, les renuméroter, les copier, lire ou écrire des fichiers seq.
Un exemple de première utilisation pourrait être quelque chose comme ceci :
from pysiril.siril import Siril
from pysiril.wrapper import Wrapper
from pysiril.addons import Addons
app=Siril() # Starts pySiril
cmd=Wrapper(app) # Starts the command wrapper
help(Siril) # Get help on Siril functions
help(Wrapper) # Get help on all Wrapper functions
help(Addons) # Get help on all Addons functions
cmd.help() # Lists of all commands
cmd.help('bgnoise') # Get help for bgnoise command
del app
Exemple de traitement avec un wrapper de commande #
L’exemple ci-dessous montre comment :
- Définir des blocs de commandes
- Démarrer pySiril, Siril et l’encapsuleur de commandes
- Définir certaines préférences
- Préparer masterbias, masterflat et masterdark
- Pré-traiter les brutes avec masterflat et masterdark, enregistrer et empiler.
- Fermer Siril et sortir correctement
Ce flux de travail de traitement est similaire au script Siril standard, OSC_Preprocessing
.
import sys
import os
from pysiril.siril import *
from pysiril.wrapper import *
# ==============================================================================
# EXAMPLE OSC_Processing with functions wrapper
# ==============================================================================
#1. defining command blocks for creating masters and processing lights
def master_bias(bias_dir, process_dir):
cmd.cd(bias_dir )
cmd.convert( 'bias', out=process_dir, fitseq=True )
cmd.cd( process_dir )
cmd.stack( 'bias', type='rej', sigma_low=3, sigma_high=3, norm='no')
def master_flat(flat_dir, process_dir):
cmd.cd(flat_dir )
cmd.convert( 'flat', out=process_dir, fitseq=True )
cmd.cd( process_dir )
cmd.preprocess( 'flat', bias='bias_stacked' )
cmd.stack( 'pp_flat', type='rej', sigma_low=3, sigma_high=3, norm='mul')
def master_dark(dark_dir, process_dir):
cmd.cd(dark_dir )
cmd.convert( 'dark', out=process_dir, fitseq=True )
cmd.cd( process_dir )
cmd.stack( 'dark', type='rej', sigma_low=3, sigma_high=3, norm='no')
def light(light_dir, process_dir):
cmd.cd(light_dir)
cmd.convert( 'light', out=process_dir, fitseq=True )
cmd.cd( process_dir )
cmd.preprocess('light', dark='dark_stacked', flat='pp_flat_stacked', cfa=True, equalize_cfa=True, debayer=True )
cmd.register('pp_light')
cmd.stack('r_pp_light', type='rej', sigma_low=3, sigma_high=3, norm='addscale', output_norm=True, out='../result')
cmd.close()
# ==============================================================================
# 2. Starting pySiril
app=Siril()
workdir = "D:/_TraitAstro/20-SiriL/work/TestSiril"
try:
cmd=Wrapper(app) #2. its wrapper
app.Open() #2. ...and finally Siril
#3. Set preferences
process_dir = '../process'
cmd.set16bits()
cmd.setext('fit')
#4. Prepare master frames
master_bias(workdir+ '/biases' ,process_dir)
master_flat(workdir+ '/flats' ,process_dir)
master_dark(workdir+ '/darks' ,process_dir)
#5. Calibrate the light frames, register and stack them
light(workdir+ '/lights' ,process_dir)
except Exception as e :
print("\n**** ERROR *** " + str(e) + "\n" )
#6. Closing Siril and deleting Siril instance
app.Close()
del app
Bien sûr, vous pouvez utiliser le script Siril pour faire exactement la même chose. Mais maintenant, imaginez que vous n’avez pas pris de flats, pour une raison quelconque (cela ne peut pas être une bonne raison, il n’y a aucune bonne raison de ne pas prendre de flat !). Avec de petites modifications au code ci-dessus, vous pouvez :
- tester pour vérifier si le dossier flats contient un fichier ou est lui-même présent,
- adapter le script pour sauter la préparation du master-flat,
- adapter le traitement des brutes pour éviter la calibration avec le master-flat.
Vous devrez faire ce qui suit.
Modifiez d’abord la fonction light()
pour accepter un kwarg afin d’indiquer si les flats sont présents:
def light(light_dir, process_dir,hasflats=True):
cmd.cd(light_dir)
cmd.convert( 'light', out=process_dir, fitseq=True )
cmd.cd( process_dir )
if hasflats:
cmd.preprocess( 'light', dark='dark_stacked', flat='pp_flat_stacked', cfa=True, equalize_cfa=True, debayer=True )
else:
cmd.preprocess( 'light', dark='dark_stacked', cfa=True, debayer=True )
cmd.register('pp_light')
cmd.stack('r_pp_light', type='rej', sigma_low=3, sigma_high=3, norm='addscale', output_norm=True, out='../result')
cmd.close()
Modifiez ensuite le script principal pour vérifier la présence des flats et adaptez le workflow de traitement en conséquence.
#4. Prepare master frames
flatsdir=workdir+ '/flats'
hasflats=True
if not(os.path.isdir(flatsdir)) or (len(os.listdir(flatsdir) == 0): # flats folder does not contain any file or none is present in workdir
hasflats=False
if hasflats:
master_bias(workdir+ '/biases' ,process_dir)
master_flat(workdir+ '/flats' ,process_dir)
master_dark(workdir+ '/darks' ,process_dir)
#5. Calibrate the light frames, register and stack them
light(workdir+ '/lights' ,process_dir,hasflats)
Ceci n’est qu’un exemple, vous pouvez faire de même pour les darks bien sûr, rendre tous les noms de dossier modulables, en faire un module avec des E/S, en passant le nom du répertoire de travail etc …
Exemple de traitement avec Execute #
Ce code fait la même chose que l’exemple ci-dessus, sauf qu’il utilise largement la méthode Execute de la classe Siril. Execute(‘une commande’) fonctionne exactement comme si vous tapiez ‘une commande’ dans la ligne de commande Siril.
import sys
import os
from pysiril.siril import *
# ==============================================================================
# EXAMPLE OSC_Processing with Execute function without wrapper functions
# ==============================================================================
def master_bias(bias_dir, process_dir):
app.Execute("cd " + bias_dir )
app.Execute("convert bias -out=" + process_dir + " -fitseq" )
app.Execute("cd " + process_dir )
app.Execute("stack bias rej 3 3 -nonorm")
def master_flat(flat_dir, process_dir):
app.Execute("cd " + flat_dir + "\n"
"convert flat -out=" + process_dir + " -fitseq" + "\n"
"cd " + process_dir + "\n"
"preprocess flat -bias=bias_stacked" + "\n"
"stack pp_flat rej 3 3 -norm=mul")
def master_dark(dark_dir, process_dir):
app.Execute(""" cd %s
convert dark -out=%s -fitseq
cd %s
stack dark rej 3 3 -nonorm """ % (dark_dir,process_dir,process_dir) )
def light(light_dir, process_dir):
app.Execute("cd " + light_dir)
app.Execute("convert light -out=" + process_dir + " -fitseq" )
app.Execute("cd " + process_dir )
app.Execute("preprocess light -dark=dark_stacked -flat=pp_flat_stacked -cfa -equalize-cfa -debayer" )
app.Execute("register pp_light")
app.Execute("stack r_pp_light rej 3 3 -norm=addscale -output_norm -out=../result")
app.Execute("close")
# ==============================================================================
workdir = "/home/barch/siril/work/TestSiril"
try:
app.Open( )
process_dir = '../process'
app.Execute("set16bits")
app.Execute("setext fit")
master_bias(workdir+ '/biases' ,process_dir)
master_flat(workdir+ '/flats' ,process_dir)
master_dark(workdir+ '/darks' ,process_dir)
light(workdir+ '/lights' ,process_dir)
except Exception as e :
print("\n**** ERROR *** " + str(e) + "\n" )
app.Close( )
del app
Libre à vous d’utiliser les fonctions Execute ou wrapper : vous connaissez les 2 principaux moyens de contrôler Siril avec Python.
Utiliser Addons #
L’exemple ci-dessous montre comment utiliser certaines des fonctions de la classe Addons.
import sys
import os
from pysiril.siril import *
from pysiril.wrapper import *
from pysiril.addons import *
# ==============================================================================
# Example of addons functions
# ==============================================================================
app=Siril()
try:
cmd=Wrapper(app)
fct=Addons(app)
# Create a seqfile
workdir = "D:/_TraitAstro/20-SiriL/work/TestSiril"
processdir = workdir + "/" + "xxxx"
fct.CleanFolder(processdir,ext_list=[ ".cr2",".seq"])
fct.MkDirs(processdir)
NbImage= fct.GetNbFiles(workdir+ '/lights/*.CR2')
print( "CR2 number:",NbImage)
number=fct.NumberImages(workdir+ '/lights/*.CR2',processdir,"offsets",start=10,bOverwrite=True)
if number == NbImage :
fct.CreateSeqFile(processdir+"/toto.seq", number )
else:
print("error of images number:",number, "<>",NbImage)
except Exception as e :
print("\n**** ERROR *** " + str(e) + "\n" )
app.Close()
del app
Fonctions renvoyant des valeurs #
L’exemple ci-dessous montre comment utiliser les fonctions renvoyant des valeurs. Il prend en entrée un fichier *.seq
et écrit un csv avec les valeurs renvoyées par la commande stat
.
Remarque: cet exemple est désormais obsolète grâce à la commande seqstat ajoutée dans Siril 0.99.8. Néanmoins, il peut vous donner des idées sur la façon d’utiliser ces fonctions.
import os,sys
import re
import glob
from pysiril.siril import Siril
from pysiril.wrapper import Wrapper
from distutils.util import strtobool
import pandas as pd
def Run(inputfilename):
folder,filename=os.path.split(inputfilename)
fileroot,_=os.path.splitext(filename)
os.chdir(folder)
with open(inputfilename) as f:
lines = list(line for line in (l.strip() for l in f) if line)
img=[]
for i,l in enumerate(lines):
if l.startswith('S'):
specline=l.split()
lenseq=int(specline[3])
if l.startswith('I'):
tl=l.split()
img.append(int(tl[1]))
for ff in glob.iglob(fileroot+'*.f*'):
_,fitext=os.path.splitext(ff)
if not(fitext=='.seq'):
break
app=Siril(bStable=False)
app.Open()
cmd=Wrapper(app)
res=[]
for i in range(lenseq):
fitfile='{0:s}{1:05d}{2:s}'.format(fileroot,img[i],fitext)
cmd.load(fitfile)
_,stats=cmd.stat()
for j in range(len(stats)):
stats[j]['file']=fitfile
stats[j]['image#']=img[i]
res.append(stats[j])
app.Close()
del app
data=pd.DataFrame.from_dict(res)
data.set_index(['file','image#','layer'],inplace=True)
data.reset_index(inplace=True)
data.to_csv(fileroot+'stats.csv',index=False)
if __name__ == "__main__":
args=[]
kwargs={}
for a in sys.argv[1:]:
if '=' in a:
f,v=a.split('=')
kwargs[f]=v
else:
args.append(a)
Run(*tuple(args),**kwargs)
Pour l’exécuter:
- copiez / collez ce code dans votre éditeur préféré
- enregistrez-le sous
seqstat.py
- dans un shell, tapez:
python seqstat.py "C:\Users\myusername\Pictures\astro\myseqfile_.seq"
Cela enregistrera myseqfile_stats.csv
dans le même dossier.
Appliquer une commande pour image seule à toute une séquence #
Ce dernier exemple est l’équivalent python des scripts shell décrits ici
.
Il utilise toutes les notions abordées auparavant. Copiez le code suivant et sauvez le fichier sous le nom genseqscript.py
.
# Usage
#######
# genseqscript.py command seqname [prefix ext]
# Examples:
###########
#
# Apply a median filter to all images from C:\MyImages\r_pp_light_.seq with "fit" extension and save them with "med_" prefix
# python genseqscript.py "fmedian 5 1" "C:\MyImages\r_pp_light_.seq" med_ fit
#
# Apply a 90 deg rotation w/o crop to all images from pp_light_.seq located in current folder
# python genseqscript.py "rotate 90 -nocrop" pp_light_ rot90_
# User settings
# command: the command to be applied to each image of the sequence. Enclosed in double quotes if there is more than one word
# seqname: name of the sequence, can be a full path or just a sequence namein the current directory (w or w/o .seq extension). Enclosed in double quotes if there are spaces in the path
# prefix: (optional) the prefix to be preprended to the processed file names, def: ""
# ext: (optional) chosen FITS extension, def: fits (can also be fit or fts)
import os,sys
from pysiril.siril import Siril
from pysiril.wrapper import Wrapper
from pysiril.addons import Addons
def Run(command,seqname,prefix='',ext='fits'):
print('Command to be run: {0:s}'.format(command))
print('prefix: {0:s}'.format(prefix))
print('FITS extension: {0:s}'.format(ext))
if os.path.isabs(seqname):
currdir,seqname=os.path.split(seqname)
else:
currdir=os.getcwd()
seqname,seqext=os.path.splitext(seqname)
if len(seqext)==0:
seqext='.seq'
print('Working directory: {0:s}'.format(currdir))
print('Sequence to be processed: {0:s}{1:s}'.format(seqname,seqext))
if not(os.path.isfile(os.path.join(currdir,seqname+seqext))):
print('The specified sequence does not exist - aborting')
sys.exit()
print('Starting PySiril')
app = Siril(R'C:\Program Files\SiriL\bin\siril-cli.exe')
AO = Addons(app)
cmd = Wrapper(app)
print('Starting Siril')
try:
app.Open()
seqfile = AO.GetSeqFile(os.path.join(currdir,seqname+seqext))
app.Execute('setext {:s}'.format(ext))
app.Execute('cd "{:s}"'.format(currdir))
for im in seqfile['images']:
currframenb=im['filenum']
currframe='{0:s}{1:05d}.{2:s}'.format(seqname,currframenb,ext)
if not(os.path.isfile(os.path.join(currdir,currframe))):
print('First file {0:s} does not exist... check if the .seq file is valid or the selected FITS extension ("{1:s}" defined here) matches your files - aborting'.format(currframe,ext))
sys.exit()
print('processing file: {0:s}'.format(currframe))
savename=prefix+currframe
cmd.load(currframe)
app.Execute(command)
cmd.save(savename)
app.Close()
del app
except Exception as e :
print("\n**** ERROR *** " + str(e) + "\n" )
if __name__ == "__main__":
args=[]
kwargs={}
for a in sys.argv[1:]:
if '=' in a:
f,v=a.split('=',1)
kwargs[f]=v
else:
args.append(a)
Run(*tuple(args),**kwargs)