Vorlagen für Skripte mit GUIs

The following templates show how to create scripts with a GUI. The first template is the simplest and only provides a GUI interface:

gui_template.py
  1# Template for Siril script with GUI only (no CLI)
  2# SPDX-License-Identifier: # insert license identifier here
  3# insert author's name and details here
  4
  5"""
  6This script template provides an exemplar for use in constructing sirilpy
  7scripts that provide a Tkinter GUI. Such scripts are only usable via the GUI.
  8This is a simplified version without command-line argument support.
  9"""
 10
 11# Core module imports
 12import os
 13import sys
 14import math
 15import tkinter as tk
 16from tkinter import ttk, filedialog, messagebox
 17import numpy as np
 18
 19import sirilpy as s
 20from sirilpy import tksiril, SirilError
 21s.ensure_installed("ttkthemes")
 22# Add other calls to ensure_installed() here to ensure that any non-core
 23# modules that are required by the script are installed and available
 24# to be imported.
 25
 26# Note: pylint will complain that the following imports are not at the
 27# top of the module. This deviation from python style is required in order
 28# to run ensure_installed() to ensure that any non-core modules are
 29# available before attempting to import them.
 30from ttkthemes import ThemedTk
 31# Add any additional imports here
 32
 33VERSION = "1.0.0"
 34
 35def template_algorithm(fit,
 36                       example_float_var,
 37                       example_bool_var,
 38                       example_file_path_var):
 39    """
 40    Template processing function, insert your algorithm here.
 41    This is the non-GUI part of the script and is entirely up to you to write.
 42    Although presented in the template as a single method it may comprise
 43    classes, methods and anything else.
 44    """
 45    print(f"Argument: {example_float_var}")
 46    print(f"Bool var: {example_bool_var}")
 47    print(f"File path: {example_file_path_var}")
 48    return fit
 49
 50class TemplateScriptInterface:
 51    """ This class provides the GUI and related callbacks """
 52    def __init__(self, root):
 53        self.root = root
 54        self.root.title(f"Template Script Interface - v{VERSION}")
 55        self.root.resizable(False, False)
 56        self.style = tksiril.standard_style()
 57
 58        # Initialize Siril connection
 59        self.siril = s.SirilInterface()
 60
 61        try:
 62            self.siril.connect()
 63        except s.SirilConnectionError:
 64            self.siril.error_messagebox("Failed to connect to Siril")
 65            return
 66
 67        # Initial checks: example - check if an image is loaded
 68        if not self.siril.is_image_loaded():
 69            self.siril.error_messagebox("No image is loaded")
 70            return
 71
 72        # Check if the version of Siril is high enough
 73        try:
 74            self.siril.cmd("requires", "1.3.6")
 75        except s.CommandError:
 76            return
 77
 78        # Create the UI and match its theme to Siril
 79        self.create_widgets()
 80        tksiril.match_theme_to_siril(self.root, self.siril)
 81
 82    # This function is used to round values to n decimal places, it is
 83    # always useful if you have sliders in your UI.
 84    def _floor_value(self, value, decimals=2):
 85        """Floor a value to the specified number of decimal places"""
 86        factor = 10 ** decimals
 87        return math.floor(value * factor) / factor
 88
 89    # Here is an example function to round the displayed value of a
 90    # variable to 2 decimal places. You will need one of these for each
 91    # slider control you have.
 92    def _update_example_float_variable_display(self, *args): # pylint: disable=unused-argument
 93        """Update the displayed template variable value with floor rounding"""
 94        value = self.example_float_var.get()
 95        rounded_value = self._floor_value(value)
 96        self.example_float_var_display_var.set(f"{rounded_value:.2f}")
 97
 98    def _browse_file(self):
 99        """
100        Use a TK filedialog to browse for a file. Note that this callback is
101        specific to the variable being updated; you might need to have several
102        similar callbacks if you have more than one file selection widget.
103        """
104        filename = filedialog.askopenfilename(
105            title="Select File",
106            initialdir=os.path.expanduser("~")
107        )
108        if filename:
109            self.example_file_path_var.set(filename)
110
111    def create_widgets(self):
112        """Create the GUI's widgets, connect signals etc. """
113        # Main frame with no padding
114        main_frame = ttk.Frame(self.root)
115        main_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
116
117        # Title
118        title_label = ttk.Label(
119            main_frame,
120            text="Template Script",
121            style="Header.TLabel"
122        )
123        title_label.pack(pady=(0, 20))
124
125        # Parameters frame
126        params_frame = ttk.LabelFrame(main_frame, text="Parameters", padding=10)
127        params_frame.pack(fill=tk.X, padx=5, pady=5)
128
129        ############################################################
130        # Example control with an embedded variable in its own frame
131        ############################################################
132
133        template_variable_frame = ttk.Frame(params_frame)
134        template_variable_frame.pack(fill=tk.X, pady=5)
135
136        ttk.Label(template_variable_frame,
137                  text="Template example_float_var:").pack(side=tk.LEFT)
138
139        # Initialize variables with default values
140        self.example_float_var = tk.DoubleVar(self.root, value=1.0)
141        self.example_float_var_display_var = tk.StringVar(self.root, value="1.00")
142
143        # Add trace to update display when slider changes
144        self.example_float_var.trace_add("write",
145                                         self._update_example_float_variable_display)
146
147        example_float_var_scale = ttk.Scale(
148            template_variable_frame,
149            from_=0.0, # set your range minimum here
150            to=1.0,    # set your range maximum here
151            orient=tk.HORIZONTAL, # oriented horizontally
152            variable=self.example_float_var, # the tk variable the widget controls
153            length=200
154        )
155        example_float_var_scale.pack(side=tk.LEFT, padx=10, expand=True)
156        ttk.Label(
157            template_variable_frame,
158            textvariable=self.example_float_var_display_var, # var truncated to 2 d.p.
159            width=5,
160            style="Value.TLabel"
161        ).pack(side=tk.LEFT)
162        tksiril.create_tooltip(example_float_var_scale,
163                               "Adjusts the template variable.")
164
165        ###############################
166        # Add frame for other variables
167        ###############################
168        options_frame = ttk.LabelFrame(main_frame, text="Options", padding=10)
169        options_frame.pack(fill=tk.X, padx=5, pady=10)
170
171        ##################
172        # Example checkbox
173        ##################
174        self.example_bool_var = tk.BooleanVar(self.root, value=False)
175        example_checkbox = ttk.Checkbutton(
176            options_frame,
177            text="Example checkbox variable",
178            variable=self.example_bool_var,
179            style="TCheckbutton"
180        )
181        example_checkbox.pack(anchor=tk.W, pady=2)
182        tksiril.create_tooltip(example_checkbox, "Example checkbox.")
183
184        ##########################################################
185        # Example file selection
186        # using an entry and a callback that triggers a filedialog
187        # These file selector widgets have their own frame
188        ##########################################################
189        file_frame = ttk.Frame(options_frame)
190        file_frame.pack(fill=tk.X, pady=5)
191
192        ttk.Label(file_frame, text="File:").pack(side=tk.LEFT)
193
194        self.example_file_path_var = tk.StringVar(self.root, value="")
195        example_file_entry = ttk.Entry(
196            file_frame,
197            textvariable=self.example_file_path_var,
198            width=40
199        )
200        example_file_entry.pack(side=tk.LEFT, padx=(5, 5), expand=True)
201
202        ttk.Button(
203            file_frame,
204            text="Browse",
205            command=self._browse_file,
206            style="TButton"
207        ).pack(side=tk.LEFT)
208
209        # The template shows examples of sliders and checkboxes
210        # however you can add other sorts of TKinter widgets here
211
212        # Buttons frame
213        button_frame = ttk.Frame(main_frame)
214        button_frame.pack(pady=20)
215
216        close_btn = ttk.Button(
217            button_frame,
218            text="Close",
219            command=self.close_dialog,
220            style="TButton"
221        )
222        close_btn.pack(side=tk.LEFT, padx=5)
223        tksiril.create_tooltip(close_btn,
224                               "Close the interface and disconnect from Siril. "
225                               "No changes will be made to the current image.")
226
227        apply_btn = ttk.Button(
228            button_frame,
229            text="Apply",
230            command=self.apply_changes,
231            style="TButton"
232        )
233        apply_btn.pack(side=tk.LEFT, padx=5)
234        tksiril.create_tooltip(apply_btn,
235                               "Apply the script function with the set parameters "
236                               "to the current image. Changes can be undone using "
237                               "Siril's undo function.")
238
239    def apply_changes(self):
240        """ Get the necessary variables from the GUI and call the algorithm """
241        try:
242            # Get the thread
243            with self.siril.image_lock():
244                # Get values from the GUI widgets
245                example_float_var = self.example_float_var.get()
246                example_bool_var = self.example_bool_var.get()
247                example_file_path_var = self.example_file_path_var.get()
248
249                # Get current image
250                fit = self.siril.get_image()
251                fit.ensure_data_type(np.float32)
252
253                # Save original image for undo
254                self.siril.undo_save_state(f"Script algorithm: "
255                                           "arg={example_float_var:.2f}")
256
257                # Apply script algorithm
258                # Your image processing functions go here
259                result = template_algorithm(fit, example_float_var, example_bool_var,
260                                           example_file_path_var)
261
262                # Clip and update image data
263                fit.data[:] = np.clip(result, 0, 1)
264                self.siril.set_image_pixeldata(fit.data)
265
266        except SirilError as e:
267            messagebox.showerror("Error", str(e))
268
269    def close_dialog(self):
270        """ Close dialog """
271        self.root.quit()
272        self.root.destroy()
273
274def main():
275    """ Main entry point """
276    try:
277        # Create the GUI interface
278        root = ThemedTk()
279        TemplateScriptInterface(root)
280        root.mainloop()
281    except SirilError as e:
282        print(f"Error initializing script: {str(e)}", file=sys.stderr)
283        sys.exit(1)
284
285if __name__ == "__main__":
286    main()
287
288language = "de"
289locale_dirs = ["../../translated/"]
290gettext_compact = "siril-documentation"

Download gui_template.py

The second template is very similar, but provides an alternative interface using a command-line argument vector. This allows the script to be called with arguments using the pyscript Siril command: it is useful for scripting algorithms that you might wish to use as part of a bigger scripted workflow.

gui_and_args_template.py
  1# Template for Siril script with a GUI
  2# SPDX-License-Identifier: # insert license identifier here
  3# insert author's name and details here
  4
  5"""
  6This script template provides an exemplar for use in constructing sirilpy
  7scripts that provide a Tkinter GUI but which can also be called using an
  8argument vector (i.e. so that they can be scripted for use with the Siril
  9pyscript command). A similar template exists for pure GUI scripts, which
 10is slightly simpler owing to not having to accommodate command-line
 11arguments.
 12"""
 13
 14# Core module imports
 15import os
 16import sys
 17import math
 18import argparse
 19import tkinter as tk
 20from tkinter import ttk, filedialog, messagebox
 21import numpy as np
 22
 23import sirilpy as s
 24from sirilpy import tksiril, SirilError
 25s.ensure_installed("ttkthemes")
 26# Add other calls to ensure_installed() here to ensure that any non-core
 27# modules that are required by the script are installed and available
 28# to be imported.
 29
 30# Note: pylint will complain that the following imports are not at the
 31# top of the module. This deviation from python style is required in order
 32# to run ensure_installed() to ensure that any non-core modules are
 33# available before attempting to import them.
 34from ttkthemes import ThemedTk
 35# Add any additional imports here
 36
 37VERSION = "1.0.0"
 38
 39def template_algorithm(fit, example_float_var,
 40                       example_bool_var,
 41                       example_file_path_var):
 42    """
 43   Template processing function, insert your algorithm here.
 44   This is the non-GUI part of the script and is entirely up to you to write.
 45   Although presented in the template as a single method it may comprise
 46   classes, methods and anything else.
 47   """
 48    print(f"Argument: {example_float_var}")
 49    print(f"Bool var: {example_bool_var}")
 50    print(f"Bool var: {example_file_path_var}")
 51    return fit
 52
 53class TemplateScriptInterface:
 54    """ This class provides the GUI and related callbacks """
 55    def __init__(self, root=None, cli_args=None):
 56        # If no CLI args, create a default namespace with defaults
 57        if cli_args is None:
 58            parser = argparse.ArgumentParser()
 59            parser.add_argument("-example_float_var", type=float, default=1.0)
 60            parser.add_argument("-example_bool_var", type=bool, default=False)
 61            parser.add_argument("-example_file_path_var", type=str, default="")
 62            # add other arguments to match the ones in main()
 63            cli_args = parser.parse_args([])
 64
 65        self.cli_args = cli_args
 66        self.root = root
 67
 68        if root:
 69            self.root.title(f"Template Script Interface - v{VERSION}")
 70            self.root.resizable(False, False)
 71            self.style = tksiril.standard_style()
 72
 73        # Initialize Siril connection
 74        self.siril = s.SirilInterface()
 75
 76        try:
 77            self.siril.connect()
 78        except s.SirilConnectionError:
 79            if root:
 80                self.siril.error_messagebox("Failed to connect to Siril")
 81            else:
 82                print("Failed to connect to Siril")
 83            return
 84
 85        # Initial checks: example - check if an image is loaded
 86        if not self.siril.is_image_loaded():
 87            if root:
 88                self.siril.error_messagebox("No image is loaded")
 89            else:
 90                print("No image is loaded")
 91            return
 92
 93        # Check if the version of Siril is high enough
 94        # 1.3.6 is the baseline when python scripting became available but in time
 95        # you may need to check for a higher version number in order to use newer
 96        # features.
 97        try:
 98            self.siril.cmd("requires", "1.3.6")
 99        except s.CommandError:
100            return
101
102        # Create the UI and match its theme to Siril
103        if root:
104            self.create_widgets()
105            tksiril.match_theme_to_siril(self.root, self.siril)
106
107        # Only apply changes if CLI arguments are non-default
108        if cli_args and (cli_args.example_float_var != 1.0):
109            self.apply_changes(from_cli=True)
110
111    # This function is used to round values to n decimal places, it is
112    # always useful if you have sliders in your UI.
113    def _floor_value(self, value, decimals=2):
114        """Floor a value to the specified number of decimal places"""
115        factor = 10 ** decimals
116        return math.floor(value * factor) / factor
117
118    # Here is an example function to round the displayed value of a
119    # variable to 2 decimal places. You will need one of these for each
120    # slider control you have.
121
122    def _update_example_float_variable_display(self, *args): # pylint: disable=unused-argument
123        """Update the displayed template variable value with floor rounding"""
124        value = self.example_float_var.get()
125        rounded_value = self._floor_value(value)
126        self.example_float_var_display_var.set(f"{rounded_value:.2f}")
127
128    def _browse_file(self):
129        """
130        Use a TK filedialog to browse for a file. Note that this callback is
131        specific to the variable being updated; you might need to have several
132        similar callbacks if you have more than one file selection widget.
133        """
134        filename = filedialog.askopenfilename(
135            title="Select File",
136            initialdir=os.path.expanduser("~")
137        )
138        if filename:
139            self.example_file_path_var.set(filename)
140
141    def create_widgets(self):
142        """Create the GUI's widgets, connect signals etc. """
143        # Main frame with no padding
144        main_frame = ttk.Frame(self.root)
145        main_frame.pack(fill=tk.BOTH, expand=True, padx=0, pady=0)
146
147        # Title
148        title_label = ttk.Label(
149            main_frame,
150            text="Template Script",
151            style="Header.TLabel"
152        )
153        title_label.pack(pady=(0, 20))
154
155        # Parameters frame
156        params_frame = ttk.LabelFrame(main_frame, text="Parameters", padding=10)
157        params_frame.pack(fill=tk.X, padx=5, pady=5)
158
159        ############################################################
160        # Example control with an embedded variable in its own frame
161        ############################################################
162
163        template_variable_frame = ttk.Frame(params_frame)
164        template_variable_frame.pack(fill=tk.X, pady=5)
165
166        ttk.Label(template_variable_frame,
167            text="Example floting point parameter:").pack(side=tk.LEFT)
168        # This is the actual float variable
169        self.example_float_var = tk.DoubleVar(self.root,
170            value=self.cli_args.example_float_var)
171        # This is a string representation of the float var limited to 2 decimal places
172        # It is used in the scale label
173        self.example_float_var_display_var = tk.StringVar(
174            self.root,
175            value=f"{self._floor_value(self.cli_args.example_float_var):.2f}")
176        # Add trace to update display when slider changes
177        self.example_float_var.trace_add("write",
178            self._update_example_float_variable_display)
179
180        example_float_var_scale = ttk.Scale(
181            template_variable_frame,
182            from_=0.0, # set your range minimum here
183            to=1.0,    # set your range maximum here
184            orient=tk.HORIZONTAL, # oriented horizontally
185            variable=self.example_float_var, # the tk variable the widget controls
186            length=200
187        )
188        example_float_var_scale.pack(side=tk.LEFT, padx=10, expand=True)
189        ttk.Label(
190            template_variable_frame,
191            textvariable=self.example_float_var_display_var, # var truncated to 2 d.p.
192            width=5,
193            style="Value.TLabel"
194        ).pack(side=tk.LEFT)
195        tksiril.create_tooltip(example_float_var_scale,
196            "Adjusts the template variable.")
197
198        ###############################
199        # Add frame for other variables
200        ###############################
201        options_frame = ttk.LabelFrame(main_frame, text="Options", padding=10)
202        options_frame.pack(fill=tk.X, padx=5, pady=10)
203
204        ##################
205        # Example checkbox
206        ##################
207        self.example_bool_var = tk.BooleanVar(self.root,
208            value=self.cli_args.example_bool_var
209        )
210        example_checkbox = ttk.Checkbutton(
211            options_frame,
212            text="Example checkbox variable",
213            variable=self.example_bool_var,
214            style="TCheckbutton"
215        )
216        example_checkbox.pack(anchor=tk.W, pady=2)
217        tksiril.create_tooltip(example_checkbox, "Example checkbox.")
218
219        ##########################################################
220        # Example file selection
221        # using an entry and a callback that triggers a filedialog
222        ##########################################################
223        file_frame = ttk.Frame(options_frame)
224        file_frame.pack(fill=tk.X, pady=5)
225
226        self.example_file_path_var = tk.StringVar(self.root,
227            value=self.cli_args.example_file_path_var)
228        example_file_entry = ttk.Entry(
229            file_frame,
230            textvariable=self.example_file_path_var,
231            width=40
232        )
233        example_file_entry.pack(side=tk.LEFT, padx=(0, 5), expand=True)
234
235        ttk.Button(
236            file_frame,
237            text="Browse",
238            command=self._browse_file,
239            style="TButton"
240        ).pack(side=tk.LEFT)
241
242        # The template shows examples of sliders and checkboxes
243        # however you can add other sorts of TKinter widgets here
244
245        # Buttons frame
246        button_frame = ttk.Frame(main_frame)
247        button_frame.pack(pady=20)
248
249        close_btn = ttk.Button(
250            button_frame,
251            text="Close",
252            command=self.close_dialog,
253            style="TButton"
254        )
255        close_btn.pack(side=tk.LEFT, padx=5)
256        tksiril.create_tooltip(close_btn,
257                               "Close the interface and disconnect from Siril. No "
258                               "changes will be made to the current image.")
259
260        apply_btn = ttk.Button(
261            button_frame,
262            text="Apply",
263            command=self.apply_changes,
264            style="TButton"
265        )
266        apply_btn.pack(side=tk.LEFT, padx=5)
267        tksiril.create_tooltip(apply_btn,
268                               "Apply the script function with the set parameters "
269                               "to the current image. Changes can be undone using "
270                               "Siril's undo function.")
271
272    def apply_changes(self, from_cli=False):
273        """
274        Get the necessary variables from CLI args or the GUI and call the algorithm
275        """
276        try:
277            # Get the thread
278            with self.siril.image_lock():
279                # Determine parameters: prefer CLI args if provided,
280                # else use GUI values
281                if from_cli and self.cli_args:
282                    example_float_var = self.cli_args.example_float_var
283                    example_bool_var = self.cli_args.example_bool_var
284                    example_file_path_var = self.cli_args.example_file_path_var
285                else:
286                    example_float_var = self.example_float_var.get()
287                    example_bool_var = self.example_bool_var.get()
288                    example_file_path_var = self.example_file_path_var.get()
289
290                # Get current image
291                fit = self.siril.get_image()
292                fit.ensure_data_type(np.float32)
293
294                # Save original image for undo
295                self.siril.undo_save_state(f"Script algorithm: "
296                                           "arg={example_float_var:.2f}")
297
298                # Apply script algorithm
299                # Your image processing functions go here
300                result = template_algorithm(fit, example_float_var,
301                                            example_bool_var,
302                                            example_file_path_var)
303
304                # Clip and update image data
305                fit.data[:] = np.clip(result, 0, 1)
306                self.siril.set_image_pixeldata(fit.data)
307
308            if from_cli:
309                print("Script algorithm applied successfully.")
310
311        except SirilError as e:
312            if from_cli:
313                print(f"Error: {str(e)}")
314            else:
315                messagebox.showerror("Error", str(e))
316
317    def close_dialog(self):
318        """ Close dialog """
319        if hasattr(self, 'root'):
320            self.root.quit()
321            self.root.destroy()
322
323def main():
324    """ Main entry point """
325    parser = argparse.ArgumentParser(description="Template python script")
326    parser.add_argument("-example_float_var", type=float, default=1.0,
327                        help="Describe the variable here (0.0 to 1.0)")
328    parser.add_argument("-example_bool_var", type=bool, default=False,
329                        help="Describe the variable here (bool, default is False)")
330    parser.add_argument("-example_file_path_var", type=str, default="",
331                        help="Describe the variable here (example file path)")
332
333    args = parser.parse_args()
334
335    try:
336        if any([args.example_float_var != 1.0, args.example_bool_var is not False]):
337            # CLI mode
338            TemplateScriptInterface(cli_args=args)
339        else:
340            # GUI mode
341            root = ThemedTk()
342            TemplateScriptInterface(root)
343            root.mainloop()
344    except SirilError as e:
345        print(f"Error initializing script: {str(e)}", file=sys.stderr)
346        sys.exit(1)
347
348if __name__ == "__main__":
349    main()
350
351language = "de"
352locale_dirs = ["../../translated/"]
353gettext_compact = "siril-documentation"

Download gui_and_args_template.py