Python Scripts

New to Siril 1.4, it is possible to write more advanced scripts using python.

Tip

Python scripting is still EXPERIMENTAL and the API is expected to become more advanced during the 1.5 development cycle.

In its current form, python scripting allows you to:

  • Execute any siril command with the added benefit of using a proper programming language so that variables passed to commands can be the result of calculations.

  • Access key Siril variables and data structures to aid in scriptwriting.

  • Access python extensions.

    • Using the 1.4 scripting interface it is possible to write a plugin that saves the current image, opens it using astropy and applies processing techniques provided by astropy, then saves the result and reopens it as the Siril main image.

    • The tk python extension can be used to create graphical user interfaces for your scripts so that user input can be obtained directly.

Warning

Run-time Dependencies

To support installation and configuration of the siril python module and its dependencies, a Python venv ("virtual environment") is configured.

Linux

This requires that there is a functional Python installation on the system _including the pip and venv modules. These are available by default as part of the flatpak and appimage distributions, but you will need to install them yourself if building Siril from source. On Debian-based systems you need the python3-pip and python3-venv packages installed; the installation requirements on other systems may vary.

Windows

If you have a Python installation in your system, the script will use that as default. If you don't, don't panic. Siril installed through the official installer also comes with a light-weight Python installation and all the required modules. That is for the official releases. If you build the development version from sources, you will need to have Python installed in your system.

macOS

The necessary dependencies are included in the official MacOS package. We do not provide support for homebrew building, but if you do build yourself on Mac then you will need to ensure you have a functional system python installation.

How does this relate to pySiril?

pySiril is a separate module, published by a separate development team: it is an established product and is already used to implement important helper apps such as Sirilic.

Philosophically, pySiril and the built-in Siril python module are quite different: whereas the built-in module is intended to be called from an already-running Siril to provide scripting functionality and spawns a python3 process to run the script, pySiril is intended for buildin applications that need to call on Siril functionality, and it spawns a Siril process from within its host Python app.

These use cases are quite different, and there is no plan to merge the two modules.

Importing the Module

Siril will take care of setting up a venv (new style python virtual environment) and installing the module in it. In your script you import it using import siril.

For convenience, you can abbreviate it as import sirilpy as s.

Initialising the Connection

Once you have imported the module, you need to establish the interface with Siril. This is done as follows:

import sirilpy as s

siril = s.SirilInterface()
try:
   siril.connect()
   print("Connected successfully!")
except SirilConnectionError as e:
   print(f"Connection failed: {e}")

(The status reporting is optional but is good practice.)

Executing Siril Commands

To execute a Siril command, once the interface is established, you use the siril.cmd() function. The command and its arguments are provided as a list, for example:

siril.cmd("findstar", "-maxstars=1000")

will run the findstar command, setting a limit of maximum 1000 stars.

The real power of using python to script commands is that you can use python variables in the command using the formatted string notation:

x = 1000
siril.cmd("findstar", f"-maxstars={x}")

This is a simple example, but the ability to define parameters to pass to image processing functions is a giant leap for Siril scripting compared with legacy Siril script files.

Tip

Most Siril commands execute an action (register, stack, asinh etc). These will function exactly the same when called from python.

However some simply print information to the Siril log, such as stat. Note that these commands do not behave any differently when called from python using siril.cmd("stat") or similar: they will still just print information to the Siril log and do not return any useful data to the python script.

In order to access the values from python you need to use a method that gets information about the image or sequence. So in this example, you would call img = siril.get_image() and then the statistics would be available in img.stats.

Warning

The cmd() method does not have a return value but will raise an exception if the command fails. This provides safe default behaviour - the script will stop on failure of a command and print an exception error message to the Siril console. If you want to handle failure more gracefully, for example by showing an error messagebox, you will need to use an exception handler like this:

try:
    siril.cmd("requires", "1.5.0")
except:
    siril.error_messagebox("This script is not compatible with this version of Siril")
    quit()

For debug purposes you may wish to have commands return a bool. This can be done using a simple wrapper such as the following:

def cmd_with_check(*args):
try:
    siril.cmd(*args)
except Exception as e:
    err_msg = ' '.join(list(args))
    print(f'Exception caught: {e}',)
    print(f"Command failed with arguments: {err_msg} but continuing...")
    return False
return True

Access to Siril variables and data structures

The siril module provides access to key Siril variables. The available data is as follows:

  • Current Siril working directory

  • Siril user config directory (for storing script-specific config)

  • Current Siril image filename

  • Current Siril image:

    • Image pixel data

    • Image ICC profile (as bytes: you will need to use a module such as pillow to convert this raw data to something useful)

    • Image metadata: all the metadata that Siril uses internally, including relevant keywords and image statistics. The full FITS header and a string containing keywords not recognized by Siril are also available.

  • Current Siril sequence:

    • Sequence frame pixel data

    • Sequence metadata: all the metadata for the currently loaded sequence is available, with the exception of some that relates to details of the sequence format that are abstracted away by the python interface and some relating to photometry series that is not implemented yet. This includes statistics for every channel of every frame, registration data for channels for which it is available of every frame and image quality data for every frame.

    • Sequence frame ICC profiles are not available as these are not generally relevant until after the sequence processing stage. If a convincing use case emerges this could be revisited as an update to the API in the 1.5 development cycle.

    • Sequence frame HISTORY and FITS header strings are not available as they are not considered useful for sequence operations, however the keywords for a sequence frame are available using the siril.get_seq_frame() method, which returns a FFit object with the keywords member populated. This provides enough metadata to support any currently identified use case.

  • Star modelling data. When stars have been detected in an image the modelling data is available as a list of star parameters for every detected star in the image.

This data is stored in the key data structures FFit representing the Siril fits data structure and Sequence representing the Siril sequence data structure.

# Get the current image
img = siril.get_image()
# Get its dimensions
siril.log(f"Current image dimensions: {img.shape[1]} x {img.shape[0]}, {img.shape[2]} channels.")

Note that the image shape is stored in a typical python order: shape[0] = height, shape[1] = width, shape[2] = channels. The width, height and channels are also directly accessible using the properties img.width, img.height and img.channels once img = get_image() has been called.

When the current image is obtained using siril.get_image(), the pixel data can be accessed as a numpy array, allowing directly operating on pixel data.

import siril
import numpy as np
# Set up the interface as above
# ...

# Try to get the current image, do something to it and update it in Siril
with siril.image_lock():
   img = siril.get_image()
   img.data[:] *= 2
   siril.set_image_pixeldata(img.data)

except Exception as e:
   raise SirilException(f"Error changing pixel data: {e}")

We just accessed the pixel array and multiplied every pixel value by 2! Note that we have to call the siril.set_image_pixeldata() function to update the data back to Siril. Using siril.set_image_pixeldata() you can even update the image to a different size or shape, or change from 16-bit unsigned data to 32-bit floating point or vice versa.

Tip

In order for siril.set_image_pixeldata() to succeed you must claim a lock on the image. This ensures that nothing else can try to update the image at the same time as you are doing, and also ensures that you can't update the image while something else is already doing so. Note that the example above uses the context handler siril.image_lock() before getting the image from Siril, not just before updating it. This ensures the image is locked throughout the time it is being processed until it is updated back in Siril and the lock is finally released.

In a similar way you can access image statistics, FITS header information, bit depth and so on. You can also access sequence information, for example whether the n th image in a sequence is included, what its stats are and so on.

Installation of Python Modules

You might have noticed we are using NumPy in the previous examples. This is a dependency of the siril module and will be pulled in during initial setup of the venv. Other python modules can be imported using the standard import command, for example import astropy as ap. However, for ease of ensuring that dependencies are installed if a user doesn't already have them, the module also provides the ensure_installed() method. This is used as follows:

s.ensure_installed("astropy")
import astropy

(or, for another example,

s.ensure_installed("tifffile")
import tifffile

First a check is done to try importing the module (in this case, astropy or tifffile). If the module is not available for import, an attempt is made to install it. If the install fails, an exception is raised and the script will halt.

Tip

If a module requires installation this may take a short time, however the delay only occurs on the first run of a script. On subsequent runs the model is already installed, so the ensure_installed() check is almost instantaneous. A message in the log notifies the user that module installation is occurring.

If the check succeeds, the module can be imported as normal using import.

Tip

All other non-core modules except for numpy should be checked with ensure_installed() as this automates the installation process for users who don't already have the required modules installed.

Warning

All Siril python scripts share the same venv. When importing modules, it is important to avoid overconstraining the version requirements in order to avoid clashes between the modules required by different scripts. Only ">=" version constraints should be used, never "==" or "<=".

import sirilpy as s
s.ensure_installed("astropy")
import astropy as ap

This will automatically pull in all of astropy's dependencies as well.

Use of External Modules

Tip

The siril.FFit object type is not the same as astropy.io.fits. Whereas astropy.io.fits provides a general file-based interface to all FITS files, siril.FFit provides an interface to the data structure Siril uses to represent FITS images. The two are not directly interchangeable! Either method can be used to obtain the pixel data as a NumPy array.

Whether you obtain the pixel data directly from the siril.FFit object or using astropy.io.fits, siril modules such as astropy, photutils and matplotlib provide building blocks for a huge range of image analysis including source detection, image segmentation, source deblending and visualisation, and the ecosystem of python modules provides limitless capability.

import sirilpy as s
s.ensure_installed("astropy")
import astropy
from astropy.io import fits
with fits.open("filename.fit", mode='update') as image:
   if isinstance(image[0].data, np.ndarray):
      image[0].data *= 2

This looks much like our last example - all we do is multiply the pixel data by 2 - but now we have used astropy to open it directly from a FITS file.

API Reference

Note that the API is very new. While we will try very hard to avoid script-breaking changes to the API released with 1.4.0 throughout the stable 1.4 series, some parts of the API may evolve during the 1.5 development cycle.