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