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"
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"