starbash 0.1.9__py3-none-any.whl → 0.1.11__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,34 @@
1
+
2
+ [repo]
3
+ kind = "recipe"
4
+
5
+
6
+ [recipe]
7
+ author.name = "FIXMESiril?"
8
+ author.email = "FIXMESiril?"
9
+
10
+ [recipe.stage.master-dark]
11
+
12
+ description = "Generate master dark"
13
+
14
+ tool.name = "siril"
15
+ tool.timeout = 30
16
+
17
+ input.type = "dark"
18
+
19
+ # Look for files in input repos, finding them by using the "relative" tag they contain
20
+ input.source = "repo"
21
+ input.required = 2 # siril needs at least 2 frames to stack
22
+
23
+ # Based on the following definitions in the stage toml file...
24
+ output.dest = "repo" # write to a particular repo
25
+ output.type = "master" # write output to the special masters repo
26
+
27
+ script = '''
28
+ # Convert bias/dark Frames to .fit files
29
+ link frames -out={process_dir}
30
+ cd {process_dir}
31
+
32
+ # Stack frames
33
+ stack frames rej 3 3 -nonorm -out={output["base_path"]}
34
+ '''
@@ -8,28 +8,36 @@ author.name = "FIXMESiril?"
8
8
  author.email = "FIXMESiril?"
9
9
 
10
10
 
11
- [[stage]]
11
+ [recipe.stage.master-flat]
12
+
13
+ # See master_bias/starbash.toml for more documentation
12
14
 
13
15
  description = "Generate master flat"
14
- disabled = true # FIXME, debugging later stuff
16
+ # disabled = false
15
17
 
16
- # For any camera
17
- auto.for-camera = []
18
+ tool.name = "siril"
19
+ # tool.timeout = 15.0 # allow up to 15 seconds before we timeout and kill tool
18
20
 
19
- tool = "siril"
20
- # input.source = "session" # or auto? prefer ones in session otherwise find by in masters
21
- input.type = "flat" # look in _session_ directories, but look only for flat files
21
+ # or auto?
22
+ # find the most recent raw fits for the current instrument (as of the time of session start)
23
+ # input.source = "most-recent" # only look for the most recent set of raws for this particular type
24
+ input.type = "flat" # look in all raw repos, but look only for flat files
22
25
 
23
- # FIXME for early development we have support for simple absolute file paths with globs
24
- input.source = "path"
25
- input.path = "/workspaces/starbash/images/from_astroboy/M 27/2025-09-16/FLAT/*.fit*"
26
- input.required = true # Is at least one input file required? If true, we will skip running this stage entirely (with a warning)
26
+ # Look for files in input repos, finding them by using the "relative" tag they contain
27
+ input.source = "repo"
28
+ input.required = 2 # siril needs at least 2 frames to stack
27
29
 
28
- when = "session-config" # run once per session-config
29
- context.output = "{process_dir}/flat_s{sessionid}_c{sessionconfig}.fits"
30
+ # We require a master bias frame for this recipe. By the time our recipe is invoked
31
+ # context.master.bias will have been set to a full path to a master bias frame
32
+ input.masters = ["bias"]
30
33
 
31
- # FIXME, bias should have been added to context by two previous stages. But for now hardwire
32
- context.bias = '/workspaces/starbash/images/masters/biases/2025-09-09_stacked.fits'
34
+ # Based on the following definitions in the stage toml file...
35
+ output.dest = "repo" # write to a particular repo
36
+ output.type = "master" # write output to the special masters repo
37
+
38
+ # FIXME for early development we have support for simple absolute file paths with globs
39
+ #input.source = "path"
40
+ #input.path = "/workspaces/starbash/images/from_astroboy/M 27/2025-09-16/FLAT/*.fit*"
33
41
 
34
42
  script = '''
35
43
  # Create a sequence from the raw flat frames
@@ -37,10 +45,10 @@ script = '''
37
45
  cd {process_dir}
38
46
 
39
47
  # Calibrate the flat frames using master bias
40
- calibrate flat -bias={bias}
48
+ calibrate flat -bias={master["bias"]}
41
49
 
42
- # Stack the pre-processed (calibrated) flat frames (writes to flat_stacked.fit)
43
- stack pp_flat rej 3 3 -norm=mul -out=flat_stacked
50
+ # Stack the pre-processed (calibrated) flat frames
51
+ stack pp_flat rej 3 3 -norm=mul -out={output["base_path"]}
44
52
  '''
45
53
 
46
54
  temporaries = ["flat", "pp_flat"]
@@ -4,6 +4,7 @@
4
4
  import os
5
5
  from glob import glob
6
6
  from starbash.tool import tools
7
+ from starbash.aliases import normalize_target_name
7
8
 
8
9
  siril = tools["siril"]
9
10
 
@@ -18,11 +19,6 @@ def perhaps_delete_temps(temps: list[str]) -> None:
18
19
  os.remove(path)
19
20
 
20
21
 
21
- def normalize_target_name(name: str) -> str:
22
- """Converts a target name to an any filesystem-safe format by removing spaces"""
23
- return name.replace(" ", "").upper()
24
-
25
-
26
22
  def make_stacked(sessionconfig: str, variant: str, output_file: str):
27
23
  """
28
24
  Registers and stacks all pre-processed light frames for a given filter configuration
@@ -2,21 +2,10 @@
2
2
  [repo]
3
3
  kind = "recipe"
4
4
 
5
- # all sb.toml files can optionally contain a version section. if version of the running starbash app is out of bounds a warning message will be printed
6
- # to the user and the file will be ignored for future processing.
7
- [recipe.require.version]
8
- min="0.1.0"
9
- max="4.5.8"
10
-
11
5
  [recipe]
12
6
  author.name = "Kevin Hester"
13
7
  author.email = "kevinh@geeksville.com"
14
8
 
15
- [[stage]]
16
-
17
- description = "Extract OSC dual duo filter Ha, Oiii and Sii channels"
18
- disabled = true # FIXME, debugging later stuff
19
-
20
9
  # FIXME-somehow-specify-what-filternames are used to auto detect this recipe can be used?
21
10
  # figure out how to support dual duo vs single duo. Perhaps: the FIRST recipe that matches an auto rule
22
11
  # is used for any auto-defected defaults. If an auto match is found it will be saved in the generated starter
@@ -25,26 +14,39 @@ disabled = true # FIXME, debugging later stuff
25
14
  # non OSC people use names like LRGB or SHO
26
15
 
27
16
  # for dual duo if we see Sii assume they also have HaOiii
28
- auto.for-filter = ["SiiOiii"]
17
+ auto.require.filter = ["SiiOiii"]
29
18
  # for single duo look for this
30
- # auto.for-filter = ["HaOiii"]
31
- auto.for-camera = ["OSC"]
19
+ # auto.require.filter = ["HaOiii"]
20
+ auto.require.camera = ["OSC"]
21
+
22
+ # all sb.toml files can optionally contain a version section. if version of the running starbash app is out of bounds a warning message will be printed
23
+ # to the user and the file will be ignored for future processing.
24
+ [recipe.require.version]
25
+ min="0.1.0"
26
+ max="4.5.8"
27
+
28
+ [recipe.stage.light]
29
+
30
+ description = "Extract OSC dual duo filter Ha, Oiii and Sii channels"
31
+ # disabled = true # FIXME, debugging later stuff
32
32
 
33
- tool = "siril"
34
- when = "session-light" # run once per session-config
35
- output = "FIXME"
33
+ tool.name = "siril"
34
+
35
+ when = "session.light" # run once per session.config
36
+
37
+ input.masters = ["bias", "flat"]
36
38
 
37
39
  # FIXME, bias and flat should have been added to context by two previous stages. But for now hardwire
38
40
  # Note: they should not have filename extensions (see strip_extension in the old process.py)
39
- context.bias = '/workspaces/starbash/images/masters/biases/2025-09-09_stacked.fits'
41
+ # context.bias = '/workspaces/starbash/images/masters/biases/2025-09-09_stacked.fits'
40
42
 
41
- context.sessionid = "2025-09-16" # FIXME, generate this by looping over all sessions (from outside this file)
42
- context.sessionconfig = "SiiOiii" # FIXME generate this by looping over all session configs
43
- context.light_base = "light_s{sessionid}_c{sessionconfig}"
43
+ # context.sessionid = "2025-09-16" # FIXME, generate this by looping over all sessions (from outside this file)
44
+ # context.sessionconfig = "SiiOiii" # FIXME generate this by looping over all session configs
45
+ # context.light_base = "light_s{sessionid}_c{sessionconfig}"
44
46
 
45
47
  # FIXME until auto light finding is in
46
- input.source = "path"
47
- input.path = "/workspaces/starbash/images/from_astroboy/M 27/2025-09-16/LIGHT/*.fit*"
48
+ # input.source = "path"
49
+ # input.path = "/workspaces/starbash/images/from_astroboy/M 27/2025-09-16/LIGHT/*.fit*"
48
50
 
49
51
  script = '''
50
52
  # Create a sequence from the raw light frames, seq file goes to process_dir
@@ -63,7 +65,12 @@ script = '''
63
65
 
64
66
  temporaries = ["FIXME"]
65
67
 
66
- [[stage]]
68
+ [recipe.stage.stack]
69
+
70
+ disabled = false # not yet ready to test
71
+
72
+ # FIXME this stage should only be considered if the previous stage in this same array
73
+ # was run. It must be run inside the same tempdir (so that files from previous stage are available)
67
74
 
68
75
  # FIXME, eventually we could make it optional to even have a starbash.toml. If we find an
69
76
  # starbash.py we could introspect it for a starbash_config dict. And look inside that for description
@@ -71,11 +78,17 @@ temporaries = ["FIXME"]
71
78
 
72
79
  description = "Stack OSC dual duo filter data, with separate Ha, Oiii and Sii channels"
73
80
 
74
- context.target = "M 27" # FIXME
75
- context.targets = "/workspaces/starbash/images/processed" # FIXME, do something smarter
81
+ # context.target = "M 27" # FIXME
82
+ # context.targets = "/workspaces/starbash/images/processed" # FIXME, do something smarter
83
+
84
+ tool.name = "python"
85
+
86
+ when = "final.stack" # run once after all session/session.config processing was done
76
87
 
77
- tool = "python"
78
- when = "session-stack" # run once after all session/session-config processing was done
88
+ # Based on the following definitions in the stage toml file...
89
+ # FIXME, we should inherit this - most recipes shouldn't have to declare it
90
+ output.dest = "repo" # write to a particular repo
91
+ output.type = "processed" # write output to the special masters repo
79
92
 
80
93
  # if not specified starbash.py used
81
94
  # script-file = "script.py"
@@ -23,8 +23,8 @@ disabled = true # FIXME, we don't yet have auto selection based on filter types
23
23
  auto.for-filter = ["HaOiii"]
24
24
  auto.for-camera = ["OSC"]
25
25
 
26
- tool = "siril"
27
- when = "session-light" # run once per session-config
26
+ tool.name = "siril"
27
+ when = "session.light" # run once per session.config
28
28
  output = "FIXME"
29
29
 
30
30
  script = '''
@@ -45,8 +45,8 @@ temporaries = ["FIXME"]
45
45
 
46
46
  disabled = true # FIXME, we don't yet have auto selection based on filter types
47
47
 
48
- tool = "python"
49
- when = "session-stack" # run once after all session/session-config processing was done
48
+ tool.name = "python"
49
+ when = "session.stack" # run once after all session/session.config processing was done
50
50
 
51
51
  script-file = "script.py"
52
52
 
@@ -5,6 +5,8 @@ kind = "repo"
5
5
  [[repo-ref]]
6
6
  dir = "master_bias"
7
7
  [[repo-ref]]
8
+ dir = "master_dark"
9
+ [[repo-ref]]
8
10
  dir = "master_flat"
9
11
 
10
12
  # Note: For automated recipe finding, it is important to list more demanding recipes first. For instance:
@@ -26,14 +28,34 @@ dir = "osc_single_duo"
26
28
  #name = "setup-masters" # for flat processing, master generation etc
27
29
  #priority = 5
28
30
 
29
- [[stages]]
30
- name = "session-config" # for flat processing, master generation etc
31
+ #
32
+ # master specific stages
33
+ #
34
+ [[master-stages]]
35
+ name = "master-bias" # generate master bias frames
36
+ priority = 10
37
+ input = "bias" # only used for frames of this type
38
+
39
+ [[master-stages]]
40
+ name = "master-dark" # generate master dark frames
31
41
  priority = 10
42
+ input = "dark"
43
+
44
+ [[master-stages]]
45
+ name = "master-flat" # generate master flat frames
46
+ priority = 20
47
+ input = "flat"
48
+
49
+ #
50
+ # session specific processing stages, not currently used, for now I just do this list from code
51
+ #
32
52
 
33
53
  [[stages]]
34
- name = "session-light" # generate light frames from lights and with reference to flats/bias
54
+ name = "light" # generate light frames from lights and with reference to flats/bias
35
55
  priority = 20
56
+ input = "light" # only used for frames of this type
36
57
 
37
58
  [[stages]]
38
- name = "session-stack" # stack frames
59
+ name = "stack" # stack frames
39
60
  priority = 30
61
+ input = "light" # only used for frames of this type
starbash/selection.py CHANGED
@@ -5,6 +5,7 @@ from __future__ import annotations
5
5
  import logging
6
6
  from typing import Any, Optional, TYPE_CHECKING
7
7
  from repo import Repo
8
+ from starbash.aliases import normalize_target_name
8
9
 
9
10
 
10
11
  def where_tuple(conditions: dict[str, Any] | None) -> tuple[str, list[Any]]:
@@ -256,7 +257,11 @@ class Selection:
256
257
  if self.targets:
257
258
  # For now, just use the first target
258
259
  # TODO: Support multiple targets in queries
259
- conditions["OBJECT"] = self.targets[0] if len(self.targets) == 1 else None
260
+ conditions["OBJECT"] = (
261
+ normalize_target_name(self.targets[0])
262
+ if len(self.targets) == 1
263
+ else None
264
+ )
260
265
 
261
266
  if self.filters:
262
267
  # For now, just use the first filter
@@ -0,0 +1,10 @@
1
+ # This is a processed repository for (Starbash)[{PROJECT_URL}].
2
+ #
3
+ # This file marks the root directory of a set of generated/processed starbash output files.
4
+ #
5
+ # You generally don't need to edit this file directly - it was auto generated when you ran
6
+ # "sb repo add --processed {REPO_PATH}".
7
+ #
8
+
9
+ [repo]
10
+ kind = "processed"
starbash/tool.py CHANGED
@@ -4,9 +4,9 @@ import textwrap
4
4
  import tempfile
5
5
  import subprocess
6
6
  import re
7
-
7
+ import threading
8
+ import queue
8
9
  import logging
9
-
10
10
  import RestrictedPython
11
11
 
12
12
  logger = logging.getLogger(__name__)
@@ -161,83 +161,133 @@ def strip_comments(text: str) -> str:
161
161
  return "\n".join(lines)
162
162
 
163
163
 
164
- def tool_run(cmd: str, cwd: str, commands: str | None = None) -> None:
165
- """Executes an external tool with an optional script of commands in a given working directory."""
166
-
167
- logger.debug(f"Running {cmd} in {cwd}: stdin={commands}")
168
- result = subprocess.run(
169
- cmd, input=commands, shell=True, capture_output=True, text=True, cwd=cwd
170
- )
171
-
172
- if result.stderr:
173
- logger.warning(f"Tool error message:\n{result.stderr.strip()}")
164
+ def tool_run(
165
+ cmd: str, cwd: str, commands: str | None = None, timeout: float | None = None
166
+ ) -> None:
167
+ """Executes an external tool with an optional script of commands in a given working directory.
174
168
 
175
- if result.returncode != 0:
176
- # If we got an error, print the entire tool stdout as a warning
177
- logger.warning(f"Tool output:\n{result.stdout.strip()}")
178
- raise RuntimeError(f"Tool failed with exit code {result.returncode}")
179
- else:
180
- logger.debug("Tool command successful.")
169
+ Streams stdout and stderr in real-time to the logger, allowing you to see subprocess output
170
+ as it happens rather than waiting for completion.
171
+ """
181
172
 
182
- if result.stdout:
183
- logger.debug(f"Tool output:\n{result.stdout.strip()}")
173
+ logger.debug(f"Running {cmd} in {cwd}: stdin={commands}")
184
174
 
175
+ # Start the process with pipes for streaming
176
+ process = subprocess.Popen(
177
+ cmd,
178
+ stdin=subprocess.PIPE if commands else None,
179
+ stdout=subprocess.PIPE,
180
+ stderr=subprocess.PIPE,
181
+ shell=True,
182
+ text=True,
183
+ cwd=cwd,
184
+ )
185
185
 
186
- def executable_path(commands: list[str], name: str) -> str:
187
- """Find the correct executable path to run for the given tool"""
188
- for cmd in commands:
189
- if shutil.which(cmd):
190
- return cmd
191
- raise FileNotFoundError(f"{name} not found, you probably need to install it.")
186
+ # Send commands to stdin if provided
187
+ if commands and process.stdin:
188
+ try:
189
+ process.stdin.write(commands)
190
+ process.stdin.close()
191
+ except BrokenPipeError:
192
+ # Process may have terminated early
193
+ pass
192
194
 
195
+ # Stream output line by line in real-time
196
+ # Use threading for cross-platform compatibility (select doesn't work on Windows with pipes)
193
197
 
194
- def siril_run(temp_dir: str, commands: str, input_files: list[str] = []) -> None:
195
- """Executes Siril with a script of commands in a given working directory."""
198
+ assert process.stdout
199
+ assert process.stderr
196
200
 
197
- # siril_path = "/home/kevinh/packages/Siril-1.4.0~beta3-x86_64.AppImage"
198
- # Possible siril commands, with preferred option first
199
- siril_commands = ["org.siril.Siril", "siril-cli", "siril"]
200
- siril_path = executable_path(siril_commands, "Siril")
201
- if siril_path == "org.siril.Siril":
202
- # The executable is inside a flatpak, so run the lighter/faster/no-gui required exe
203
- # from inside the flatpak
204
- siril_path = "flatpak run --command=siril-cli org.siril.Siril"
201
+ output_queue: queue.Queue = queue.Queue()
205
202
 
206
- # Create symbolic links for all input files in the temp directory
207
- for f in input_files:
208
- os.symlink(
209
- os.path.abspath(str(f)), os.path.join(temp_dir, os.path.basename(str(f)))
210
- )
203
+ def read_stream(stream, log_func, stream_name):
204
+ """Read from stream and put lines in queue."""
205
+ try:
206
+ for line in stream:
207
+ line = line.rstrip("\n")
208
+ output_queue.put((log_func, stream_name, line))
209
+ finally:
210
+ output_queue.put((None, stream_name, None)) # Signal EOF
211
211
 
212
- # We dedent here because the commands are often indented multiline strings
213
- script_content = textwrap.dedent(
214
- f"""
215
- requires 1.4.0-beta3
216
- {textwrap.dedent(strip_comments(commands))}
217
- """
212
+ # Start threads to read stdout and stderr
213
+ stdout_thread = threading.Thread(
214
+ target=read_stream,
215
+ args=(process.stdout, logger.debug, "tool-stdout"),
216
+ daemon=True,
218
217
  )
219
-
220
- logger.info(
221
- f"Running Siril in {temp_dir}, ({len(input_files)} input files) cmds:\n{script_content}"
218
+ stderr_thread = threading.Thread(
219
+ target=read_stream,
220
+ args=(process.stderr, logger.warning, "tool-stderr"),
221
+ daemon=True,
222
222
  )
223
223
 
224
- # The `-s -` arguments tell Siril to run in script mode and read commands from stdin.
225
- # It seems like the -d command may also be required when siril is in a flatpak
226
- cmd = f"{siril_path} -d {temp_dir} -s -"
224
+ stdout_thread.start()
225
+ stderr_thread.start()
227
226
 
228
- tool_run(cmd, temp_dir, script_content)
227
+ # Track which streams have finished
228
+ streams_finished = 0
229
229
 
230
+ try:
231
+ # Process output from queue until both streams are done
232
+ while streams_finished < 2:
233
+ try:
234
+ # Use timeout to periodically check if process has terminated
235
+ log_func, stream_name, line = output_queue.get(timeout=0.1)
236
+
237
+ if log_func is None:
238
+ # EOF signal
239
+ streams_finished += 1
240
+ else:
241
+ # Log the line
242
+ log_func(f"[{stream_name}] {line}")
243
+
244
+ except queue.Empty:
245
+ # No output available, check if process terminated
246
+ if process.poll() is not None:
247
+ # Process finished, wait a bit more for remaining output
248
+ break
249
+
250
+ # Wait for threads to finish (they should be done or very close)
251
+ stdout_thread.join(timeout=1.0)
252
+ stderr_thread.join(timeout=1.0)
253
+
254
+ # Drain any remaining items in queue
255
+ while not output_queue.empty():
256
+ try:
257
+ log_func, stream_name, line = output_queue.get_nowait()
258
+ if log_func is not None:
259
+ log_func(f"[{stream_name}] {line}")
260
+ except queue.Empty:
261
+ break
230
262
 
231
- def graxpert_run(cwd: str, arguments: str) -> None:
232
- """Executes Graxpert with the specified command line arguments"""
233
-
234
- graxpert_commands = ["graxpert"]
235
- graxpert_path = executable_path(graxpert_commands, "Graxpert")
263
+ # Wait for process to complete with timeout
264
+ try:
265
+ process.wait(timeout=timeout)
266
+ except subprocess.TimeoutExpired:
267
+ process.kill()
268
+ process.wait()
269
+ raise RuntimeError(f"Tool timed out after {timeout} seconds")
270
+
271
+ returncode = process.returncode
272
+
273
+ if returncode != 0:
274
+ raise RuntimeError(f"Tool failed with exit code {returncode}")
275
+ else:
276
+ logger.debug("Tool command successful.")
277
+ finally:
278
+ # Ensure streams are properly closed
279
+ if process.stdout:
280
+ process.stdout.close()
281
+ if process.stderr:
282
+ process.stderr.close()
236
283
 
237
- # Arguments look similar to: graxpert -cmd background-extraction -output /tmp/testout tests/test_images/real_crummy.fits
238
- cmd = f"{graxpert_path} {arguments}"
239
284
 
240
- tool_run(cmd, cwd)
285
+ def executable_path(commands: list[str], name: str) -> str:
286
+ """Find the correct executable path to run for the given tool"""
287
+ for cmd in commands:
288
+ if shutil.which(cmd):
289
+ return cmd
290
+ raise FileNotFoundError(f"{name} not found, you probably need to install it.")
241
291
 
242
292
 
243
293
  class Tool:
@@ -248,6 +298,12 @@ class Tool:
248
298
 
249
299
  # default script file name
250
300
  self.default_script_file = None
301
+ self.set_defaults()
302
+
303
+ def set_defaults(self):
304
+ # default timeout in seconds, if you need to run a tool longer than this, you should change
305
+ # it before calling run()
306
+ self.timeout = 10.0
251
307
 
252
308
  def run_in_temp_dir(self, commands: str, context: dict = {}) -> None:
253
309
  """Run commands inside this tool (with cwd pointing to a temp directory)"""
@@ -275,6 +331,7 @@ class SirilTool(Tool):
275
331
  super().__init__("siril")
276
332
 
277
333
  def run(self, cwd: str, commands: str, context: dict = {}) -> None:
334
+ """Executes Siril with a script of commands in a given working directory."""
278
335
 
279
336
  # Iteratively expand the command string to handle nested placeholders.
280
337
  # The loop continues until the string no longer changes.
@@ -282,7 +339,42 @@ class SirilTool(Tool):
282
339
 
283
340
  input_files = context.get("input_files", [])
284
341
 
285
- siril_run(cwd, expanded, input_files)
342
+ temp_dir = cwd
343
+
344
+ # siril_path = "/home/kevinh/packages/Siril-1.4.0~beta3-x86_64.AppImage"
345
+ # Possible siril commands, with preferred option first
346
+ siril_commands = ["org.siril.Siril", "siril-cli", "siril"]
347
+ siril_path = executable_path(siril_commands, "Siril")
348
+ if siril_path == "org.siril.Siril":
349
+ # The executable is inside a flatpak, so run the lighter/faster/no-gui required exe
350
+ # from inside the flatpak
351
+ siril_path = "flatpak run --command=siril-cli org.siril.Siril"
352
+
353
+ # Create symbolic links for all input files in the temp directory
354
+ for f in input_files:
355
+ os.symlink(
356
+ os.path.abspath(str(f)),
357
+ os.path.join(temp_dir, os.path.basename(str(f))),
358
+ )
359
+
360
+ # We dedent here because the commands are often indented multiline strings
361
+ script_content = textwrap.dedent(
362
+ f"""
363
+ requires 1.4.0-beta3
364
+ {textwrap.dedent(strip_comments(expanded))}
365
+ """
366
+ )
367
+
368
+ logger.debug(
369
+ f"Running Siril in {temp_dir}, ({len(input_files)} input files) cmds:\n{script_content}"
370
+ )
371
+ logger.info(f"Running Siril ({len(input_files)} input files)")
372
+
373
+ # The `-s -` arguments tell Siril to run in script mode and read commands from stdin.
374
+ # It seems like the -d command may also be required when siril is in a flatpak
375
+ cmd = f"{siril_path} -d {temp_dir} -s -"
376
+
377
+ tool_run(cmd, temp_dir, script_content, timeout=self.timeout)
286
378
 
287
379
 
288
380
  class GraxpertTool(Tool):
@@ -292,7 +384,12 @@ class GraxpertTool(Tool):
292
384
  super().__init__("graxpert")
293
385
 
294
386
  def run(self, cwd: str, commands: str, context: dict = {}) -> None:
295
- graxpert_run(cwd, commands)
387
+ """Executes Graxpert with the specified command line arguments"""
388
+
389
+ # Arguments look similar to: graxpert -cmd background-extraction -output /tmp/testout tests/test_images/real_crummy.fits
390
+ cmd = f"graxpert {commands}"
391
+
392
+ tool_run(cmd, cwd, timeout=self.timeout)
296
393
 
297
394
 
298
395
  class PythonTool(Tool):
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starbash
3
- Version: 0.1.9
3
+ Version: 0.1.11
4
4
  Summary: A tool for automating/standardizing/sharing astrophotography workflows.
5
5
  License-File: LICENSE
6
6
  Author: Kevin Hester
@@ -33,6 +33,7 @@ A tool for automating/standardizing/sharing astrophotography workflows.
33
33
  * Automatic - with sensible defaults, that you can change as needed.
34
34
  * Easy - provides a 'seestar like' starting-point for autoprocessing all your sessions (by default).
35
35
  * Fast - even with large image repositories. Automatic master bias and flat generation and reasonable defaults
36
+ * Multi-session - by default. So your workflows can stack from multiple nights (and still use the correct flats etc...)
36
37
  * Sharable - you can share/use recipes for image preprocessing flows.
37
38
 
38
39
  (This project is currently 'alpha' and missing recipes for some workflows, but adding new recipes is easy and we're happy to help. Please file a github issue if your images are not auto-processed and we'll work out a fix.)
@@ -89,7 +90,7 @@ FIXME - add getting started instructions (possibly with a screenshare video)
89
90
 
90
91
  ### Repository Management
91
92
  - `sb repo list [--verbose]` - List installed repos (use `-v` for details)
92
- - `sb repo add [--master] <filepath|URL>` - Add a repository, optionally specifying the type
93
+ - `sb repo add [--master] [filepath|URL]` - Add a repository, optionally specifying the type
93
94
  - `sb repo remove <REPOURL>` - Remove the indicated repo from the repo list
94
95
  - `sb repo reindex [--force] <REPOURL>` - Reindex the specified repo (or all repos if none specified)
95
96
 
@@ -113,6 +114,7 @@ FIXME - add getting started instructions (possibly with a screenshare video)
113
114
  - `sb info target` - List targets (filtered based on the current selection)
114
115
  - `sb info telescope` - List instruments (filtered based on the current selection)
115
116
  - `sb info filter` - List all filters found in current selection
117
+ - `sb info master [KIND]` - List all precalculated master images (darks, biases, flats). Optional KIND argument to filter by image type (e.g., BIAS, DARK, FLAT)
116
118
 
117
119
  ## Not yet supported commands
118
120
 
@@ -142,4 +144,4 @@ Project members can access crash reports [here](https://geeksville.sentry.io/ins
142
144
  ## License
143
145
 
144
146
  Copyright 2025 Kevin Hester, kevinh@geeksville.com.
145
- Licensed under the (GPL v3)[LICENSE]
147
+ Licensed under the [GPL v3](LICENSE)