Modèles de scripts avec interface graphique GUIs

Les modèles suivants montrent comment créer des scripts avec un GUI. Le premier modèle est le plus simple et ne fournit qu'une interface graphique :

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 = "fr"
289locale_dirs = ["../../translated/"]
290gettext_compact = "siril-documentation"

Download gui_template.py

Le second modèle est très similaire, mais fournit une interface alternative utilisant un vecteur d'arguments en ligne de commande. Cela permet d'appeler le script avec des arguments à l'aide de la commande Siril pyscript : c'est utile pour scripter des algorithmes que vous pourriez vouloir utiliser dans le cadre d'un flux de travail scripté plus large.

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 = "fr"
352locale_dirs = ["../../translated/"]
353gettext_compact = "siril-documentation"

Download gui_and_args_template.py