starbash 0.3.0__tar.gz → 0.3.1__tar.gz

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.
Files changed (83) hide show
  1. {starbash-0.3.0 → starbash-0.3.1}/PKG-INFO +13 -6
  2. {starbash-0.3.0 → starbash-0.3.1}/README.md +12 -5
  3. {starbash-0.3.0 → starbash-0.3.1}/pyproject.toml +1 -1
  4. starbash-0.3.1/src/starbash/assets/favicon.ico +0 -0
  5. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/publish.py +16 -9
  6. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/publish/github.py +25 -5
  7. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/publish/github_service.py +54 -2
  8. starbash-0.3.1/src/starbash/recipes/crop.py +75 -0
  9. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/recipes/osc.py +0 -37
  10. starbash-0.3.1/src/starbash/templates/report/Gemfile +3 -0
  11. starbash-0.3.1/src/starbash/templates/report/__init__.py +0 -0
  12. starbash-0.3.1/src/starbash/templates/report/_config.yml +8 -0
  13. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/report/default.html +2 -1
  14. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/report/target.md.jinja +3 -1
  15. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/starnet.py +25 -4
  16. {starbash-0.3.0 → starbash-0.3.1}/LICENSE +0 -0
  17. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/__init__.py +0 -0
  18. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/aliases.py +0 -0
  19. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/analytics.py +0 -0
  20. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/app.py +0 -0
  21. {starbash-0.3.0/src/starbash/defaults → starbash-0.3.1/src/starbash/assets}/__init__.py +0 -0
  22. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/check_version.py +0 -0
  23. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/__init__.py +0 -0
  24. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/info.py +0 -0
  25. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/process.py +0 -0
  26. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/repo.py +0 -0
  27. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/select.py +0 -0
  28. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/commands/user.py +0 -0
  29. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/database.py +0 -0
  30. {starbash-0.3.0/src/starbash/recipes → starbash-0.3.1/src/starbash/defaults}/__init__.py +0 -0
  31. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/defaults/starbash.toml +0 -0
  32. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/doit.py +0 -0
  33. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/doit_types.py +0 -0
  34. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/dwarf3.py +0 -0
  35. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/exception.py +0 -0
  36. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/filtering.py +0 -0
  37. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/fits.py +0 -0
  38. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/icon.py +0 -0
  39. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/linux.py +0 -0
  40. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/main.py +0 -0
  41. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/os.py +0 -0
  42. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/parameters.py +0 -0
  43. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/paths.py +0 -0
  44. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/processed_target.py +0 -0
  45. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/processing.py +0 -0
  46. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/processing_like.py +0 -0
  47. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/publish/__init__.py +0 -0
  48. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/publish/credentials.py +0 -0
  49. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/recipes/README.md +0 -0
  50. {starbash-0.3.0/src/starbash/templates → starbash-0.3.1/src/starbash/recipes}/__init__.py +0 -0
  51. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/report.py +0 -0
  52. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/rich.py +0 -0
  53. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/safety.py +0 -0
  54. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/score.py +0 -0
  55. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/selection.py +0 -0
  56. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/sim_siril/__init__.py +0 -0
  57. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/sim_siril/connection.py +0 -0
  58. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/sim_siril/enums.py +0 -0
  59. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/sim_siril/utility.py +0 -0
  60. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/siril/import_registration.py +0 -0
  61. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/stage_utils.py +0 -0
  62. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/stages.py +0 -0
  63. {starbash-0.3.0/src/starbash/templates/report → starbash-0.3.1/src/starbash/templates}/__init__.py +0 -0
  64. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/repo/master.toml +0 -0
  65. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/repo/processed.toml +0 -0
  66. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/report/README.md.jinja +0 -0
  67. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/report/index.md.jinja +0 -0
  68. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/target/master.toml +0 -0
  69. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/target/processed/about.toml +0 -0
  70. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/target/processed/main.toml +0 -0
  71. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/target/processed/sessions.toml +0 -0
  72. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/templates/userconfig.toml +0 -0
  73. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/toml.py +0 -0
  74. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/__init__.py +0 -0
  75. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/base.py +0 -0
  76. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/context.py +0 -0
  77. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/graxpert.py +0 -0
  78. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/python.py +0 -0
  79. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/rcastro.py +0 -0
  80. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/tool/siril.py +0 -0
  81. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/ui/main.py +0 -0
  82. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/url.py +0 -0
  83. {starbash-0.3.0 → starbash-0.3.1}/src/starbash/windows.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: starbash
3
- Version: 0.3.0
3
+ Version: 0.3.1
4
4
  Summary: A tool for automating/standardizing/sharing astrophotography workflows.
5
5
  License-File: LICENSE
6
6
  Author: Kevin Hester
@@ -43,12 +43,14 @@ A tool for automating/standardizing/sharing astrophotography workflows.
43
43
  ## What is starbash?
44
44
  <img src="https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/doc/icon/generated/web/icon-192.png" alt="Starbash: Astrophotography workflows simplified" width="30%" align="right" style="margin-bottom: 20px;">
45
45
 
46
- * Automatic - with sensible defaults that you can change as needed. Just a few keystrokes to preprocess an entire repo of raw images.
47
- * Easy - provides a 'seestar-like' autoprocessing all your sessions (by default). You can optionally do manual postprocessing with all this 'grunt work' preprocessing automated.
46
+ * Automatic - with sensible defaults that you can change as needed. Just a few keystrokes to auto-process an **entire** repo of raw images. It uses sensible customizable defaults - you can optionally tweak parameters and reprocess as needed.
47
+ * Easy - provides a 'seestar-like' autoprocessing all your sessions (by default). You can optionally do manual postprocessing with all this 'grunt work' default processing automated.
48
48
  * Fast<sup>1</sup> - even with large image repositories. Automatic master bias and flat generation. Automatic preprocessing/ stacking/background-elimination with reasonable defaults.
49
- * Multi-session - by default. So your workflows can stack from multiple nights (and still use the correct flats etc...).
49
+ * Multi-session - by default. So your workflows can stack from multiple nights (and still use/auto-generate the correct flats, darks/biases etc...).
50
50
  * Shareable - you can share/use recipes for image preprocessing flows. The eventual goal is a transition away from opaque burried scripts. Recipes can come from multiple repos and you or other orgs can host recipes.
51
+ * Automatic (optional) posting/creation of your generated images and workflows to a free ['github pages'](https://docs.github.com/en/pages) website. [Sample](https://geeksville.github.io/starbash-public/targets/sh2-91/).
51
52
  * Attribution (by default) - a full set of contributions to final image can be tracked: instrument, raw images, processed-by, recipes-by/version etc...
53
+ * Full session/FITS aware history - select targets by name, date-range, instrument etc...
52
54
 
53
55
  (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.)
54
56
 
@@ -58,7 +60,7 @@ A tool for automating/standardizing/sharing astrophotography workflows.
58
60
  ## What is starbash NOT
59
61
 
60
62
  ### Not a new image processing tool (like Siril)
61
- It is important to understand that Starbash is primarily a tool for **repeatable**, **sharable**, **semi-automated** workflows. It is tool agnostic and really just focuses on the relationship between tools, images and sessions. It builds upon the great work of existing tools (currently mainly Siril and Graxpert) so we can grow an ecosystem of recipes.
63
+ It is important to understand that Starbash is primarily a tool for **repeatable**, **sharable**, **semi-automated** workflows. It is tool agnostic and really just focuses on the relationship between tools, images and sessions. It builds upon the great work of existing tools (currently mainly [Siril](https://siril.org/), [GraXpert](https://graxpert.com/), [Blurxterminator](https://www.rc-astro.com/software/bxt/) and [Starnet2](https://starnetastro.com/cli-tools/)) so we can grow an ecosystem of recipes.
62
64
 
63
65
  ### Not a set of scripts
64
66
 
@@ -98,7 +100,12 @@ If you are interested in alpha-testing we ❤️ you. This README should have e
98
100
 
99
101
  ### From the alpha 3 (0.3.0) release (2026/08/31)
100
102
 
101
- * Auto publish your finished images and workflow report to github-sites
103
+ - Added end-to-end GitHub Pages publishing, including a friendlier GitHub setup flow and automatic token refresh.
104
+ - Introduced the new processing layout with shareable per-target configuration and improved recipe/dependency handling.
105
+ - Added Starnet, BlurXTerminator, and NoiseXTerminator support, with live Siril progress and automatic star recomposition.
106
+ - Improved FITS/session reporting with focal-length and FWHM information, plus more reliable OSC stacking.
107
+ - Added safer cleanup of temporary processing files and clearer warnings when external tools are missing.
108
+ - Publishing is now about twice as fast thanks to concurrent uploads.
102
109
 
103
110
  ## Features coming soon
104
111
 
@@ -9,12 +9,14 @@ A tool for automating/standardizing/sharing astrophotography workflows.
9
9
  ## What is starbash?
10
10
  <img src="https://raw.githubusercontent.com/geeksville/starbash/refs/heads/main/doc/icon/generated/web/icon-192.png" alt="Starbash: Astrophotography workflows simplified" width="30%" align="right" style="margin-bottom: 20px;">
11
11
 
12
- * Automatic - with sensible defaults that you can change as needed. Just a few keystrokes to preprocess an entire repo of raw images.
13
- * Easy - provides a 'seestar-like' autoprocessing all your sessions (by default). You can optionally do manual postprocessing with all this 'grunt work' preprocessing automated.
12
+ * Automatic - with sensible defaults that you can change as needed. Just a few keystrokes to auto-process an **entire** repo of raw images. It uses sensible customizable defaults - you can optionally tweak parameters and reprocess as needed.
13
+ * Easy - provides a 'seestar-like' autoprocessing all your sessions (by default). You can optionally do manual postprocessing with all this 'grunt work' default processing automated.
14
14
  * Fast<sup>1</sup> - even with large image repositories. Automatic master bias and flat generation. Automatic preprocessing/ stacking/background-elimination with reasonable defaults.
15
- * Multi-session - by default. So your workflows can stack from multiple nights (and still use the correct flats etc...).
15
+ * Multi-session - by default. So your workflows can stack from multiple nights (and still use/auto-generate the correct flats, darks/biases etc...).
16
16
  * Shareable - you can share/use recipes for image preprocessing flows. The eventual goal is a transition away from opaque burried scripts. Recipes can come from multiple repos and you or other orgs can host recipes.
17
+ * Automatic (optional) posting/creation of your generated images and workflows to a free ['github pages'](https://docs.github.com/en/pages) website. [Sample](https://geeksville.github.io/starbash-public/targets/sh2-91/).
17
18
  * Attribution (by default) - a full set of contributions to final image can be tracked: instrument, raw images, processed-by, recipes-by/version etc...
19
+ * Full session/FITS aware history - select targets by name, date-range, instrument etc...
18
20
 
19
21
  (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.)
20
22
 
@@ -24,7 +26,7 @@ A tool for automating/standardizing/sharing astrophotography workflows.
24
26
  ## What is starbash NOT
25
27
 
26
28
  ### Not a new image processing tool (like Siril)
27
- It is important to understand that Starbash is primarily a tool for **repeatable**, **sharable**, **semi-automated** workflows. It is tool agnostic and really just focuses on the relationship between tools, images and sessions. It builds upon the great work of existing tools (currently mainly Siril and Graxpert) so we can grow an ecosystem of recipes.
29
+ It is important to understand that Starbash is primarily a tool for **repeatable**, **sharable**, **semi-automated** workflows. It is tool agnostic and really just focuses on the relationship between tools, images and sessions. It builds upon the great work of existing tools (currently mainly [Siril](https://siril.org/), [GraXpert](https://graxpert.com/), [Blurxterminator](https://www.rc-astro.com/software/bxt/) and [Starnet2](https://starnetastro.com/cli-tools/)) so we can grow an ecosystem of recipes.
28
30
 
29
31
  ### Not a set of scripts
30
32
 
@@ -64,7 +66,12 @@ If you are interested in alpha-testing we ❤️ you. This README should have e
64
66
 
65
67
  ### From the alpha 3 (0.3.0) release (2026/08/31)
66
68
 
67
- * Auto publish your finished images and workflow report to github-sites
69
+ - Added end-to-end GitHub Pages publishing, including a friendlier GitHub setup flow and automatic token refresh.
70
+ - Introduced the new processing layout with shareable per-target configuration and improved recipe/dependency handling.
71
+ - Added Starnet, BlurXTerminator, and NoiseXTerminator support, with live Siril progress and automatic star recomposition.
72
+ - Improved FITS/session reporting with focal-length and FWHM information, plus more reliable OSC stacking.
73
+ - Added safer cleanup of temporary processing files and clearer warnings when external tools are missing.
74
+ - Publishing is now about twice as fast thanks to concurrent uploads.
68
75
 
69
76
  ## Features coming soon
70
77
 
@@ -1,6 +1,6 @@
1
1
  [tool.poetry]
2
2
  name = "starbash"
3
- version = "0.3.0"
3
+ version = "0.3.1"
4
4
  description = "A tool for automating/standardizing/sharing astrophotography workflows."
5
5
  authors = ["Kevin Hester <kevinh@geeksville.com>"]
6
6
  readme = "README.md"
@@ -27,10 +27,10 @@ UPLOAD_PATH_BLACKLIST = ("Gemfile",)
27
27
  MAX_BLOB_UPLOADS = 4
28
28
 
29
29
 
30
- def _rewrite() -> Path:
30
+ def _rewrite(github_username: str | None = None) -> Path:
31
31
  site: Path | None = None
32
32
  with Starbash("publish.github.rewrite") as sb:
33
- site = GitHubPublisher(sb).publish()
33
+ site = GitHubPublisher(sb, github_username=github_username).publish()
34
34
  assert site is not None
35
35
  console.print(f"Generated site: {site}")
36
36
  return site
@@ -248,7 +248,18 @@ def _publish_github(dry_run: bool, login: bool) -> None:
248
248
  if credential is None:
249
249
  credential = _authenticate()
250
250
 
251
- site = _rewrite()
251
+ service: GitHubService | None = None
252
+ owner: str | None = None
253
+ if not dry_run:
254
+ assert credential is not None
255
+ service = _credential_service(credential)
256
+ if credential.needs_refresh() and credential.refresh_token:
257
+ service.apply_token_response(
258
+ service.refresh_access_token(CLIENT_ID, credential.refresh_token)
259
+ )
260
+ owner = str(service.user()["login"])
261
+
262
+ site = _rewrite(owner) if owner else _rewrite()
252
263
  files = sorted(
253
264
  path
254
265
  for path in site.rglob("*")
@@ -271,13 +282,10 @@ def _publish_github(dry_run: bool, login: bool) -> None:
271
282
  console.print("No GitHub repository, branch, commit, or Pages configuration was changed.")
272
283
  return
273
284
  assert credential is not None
285
+ assert service is not None
286
+ assert owner is not None
274
287
  with analytics_start_span(name="github", op="upload"):
275
- service = _credential_service(credential)
276
288
  try:
277
- if credential.needs_refresh() and credential.refresh_token:
278
- service.apply_token_response(
279
- service.refresh_access_token(CLIENT_ID, credential.refresh_token)
280
- )
281
289
  with Progress(
282
290
  SpinnerColumn(),
283
291
  TextColumn("[progress.description]{task.description}"),
@@ -288,7 +296,6 @@ def _publish_github(dry_run: bool, login: bool) -> None:
288
296
  ) as progress:
289
297
  operation = progress.add_task("Contacting GitHub", total=8 + len(files))
290
298
 
291
- owner = str(service.user()["login"])
292
299
  pages_url = f"https://{owner}.github.io/starbash-public/"
293
300
  progress.update(operation, description=f"Authenticated as {owner}", advance=1)
294
301
  _require_app_installation(service)
@@ -64,9 +64,15 @@ def equipment_rows(equipment: Any) -> list[dict[str, str | None]]:
64
64
  class GitHubPublisher:
65
65
  """Generate a complete local Jekyll site from one processed repository."""
66
66
 
67
- def __init__(self, sb: Any, site_dir: Path | None = None) -> None:
67
+ def __init__(
68
+ self,
69
+ sb: Any,
70
+ site_dir: Path | None = None,
71
+ github_username: str | None = None,
72
+ ) -> None:
68
73
  self.sb = sb
69
74
  self.site_dir = site_dir or get_publish_site_dir()
75
+ self.github_username = github_username
70
76
  self.environment = Environment(loader=PackageLoader("starbash", "templates/report"))
71
77
 
72
78
  def _processed_root(self) -> Path:
@@ -133,17 +139,21 @@ class GitHubPublisher:
133
139
  assets_root = self.site_dir / "assets" / "targets"
134
140
  posts.mkdir(parents=True, exist_ok=True)
135
141
  assets_root.mkdir(parents=True, exist_ok=True)
142
+ config = resources.files("starbash.templates.report").joinpath("_config.yml")
136
143
  (self.site_dir / "_config.yml").write_text(
137
- "title: Starbash Images\nmarkdown: kramdown\npermalink: pretty\ntimezone: UTC\ntheme: jekyll-theme-midnight\n"
144
+ config.read_text(encoding="utf-8"), encoding="utf-8"
138
145
  )
146
+ gemfile = resources.files("starbash.templates.report").joinpath("Gemfile")
139
147
  (self.site_dir / "Gemfile").write_text(
140
- 'source "https://rubygems.org"\n'
141
- 'gem "github-pages", group: :jekyll_plugins\n'
148
+ gemfile.read_text(encoding="utf-8"), encoding="utf-8"
142
149
  )
143
150
  layouts = self.site_dir / "_layouts"
144
151
  layouts.mkdir(exist_ok=True)
145
152
  default_layout = resources.files("starbash.templates.report").joinpath("default.html")
146
153
  (layouts / "default.html").write_text(default_layout.read_text(encoding="utf-8"), encoding="utf-8")
154
+ favicon = resources.files("starbash.assets").joinpath("favicon.ico")
155
+ with favicon.open("rb") as source, (self.site_dir / "favicon.ico").open("wb") as destination:
156
+ shutil.copyfileobj(source, destination)
147
157
  index_targets: list[dict[str, Any]] = []
148
158
  for directory, document in self._targets(root):
149
159
  about = document.get("about", {})
@@ -158,6 +168,11 @@ class GitHubPublisher:
158
168
  if isinstance(summary, str):
159
169
  about = {**about, "summary": summary}
160
170
  name = str(target.get("id") or directory.name)
171
+ description = about.get("description")
172
+ if not isinstance(description, str) or not description.strip():
173
+ description = about.get("summary")
174
+ if not isinstance(description, str) or not description.strip():
175
+ description = f"Processed Starbash target: {name}."
161
176
  slug = slugify(name)
162
177
  asset_dir = assets_root / slug
163
178
  asset_dir.mkdir(parents=True, exist_ok=True)
@@ -191,11 +206,16 @@ class GitHubPublisher:
191
206
  "chart": f"../../assets/targets/{slug}/{chart_name}",
192
207
  }
193
208
  )
209
+ page_images = [f"../../{url}" for url in image_urls]
210
+ seo_image = f"/{image_urls[0]}" if image_urls else None
194
211
  page_name = f"{slug}.md"
195
212
  post = self.environment.get_template("target.md.jinja").render(
196
213
  target={**target, "name": name},
197
214
  about=about,
198
- images=[f"../../{s}" for s in image_urls],
215
+ description=description,
216
+ github_username=self.github_username,
217
+ images=page_images,
218
+ image=seo_image,
199
219
  sessions=sessions,
200
220
  workflow_url=f"../../assets/targets/{slug}/main.toml",
201
221
  )
@@ -28,6 +28,13 @@ class GitHubAuthenticationError(GitHubError):
28
28
  """The saved access token was rejected by GitHub."""
29
29
 
30
30
 
31
+ class GitHubTimeoutError(GitHubError):
32
+ """A GitHub request timed out."""
33
+
34
+
35
+ GITHUB_REQUEST_ATTEMPTS = 3
36
+
37
+
31
38
  @dataclass(frozen=True)
32
39
  class DeviceCode:
33
40
  device_code: str
@@ -96,6 +103,37 @@ class GitHubService:
96
103
  url: str,
97
104
  payload: dict[str, Any] | None = None,
98
105
  _retried_after_refresh: bool = False,
106
+ ) -> dict[str, Any]:
107
+ """Make a GitHub request, retrying transient timeouts."""
108
+ for attempt in range(1, GITHUB_REQUEST_ATTEMPTS + 1):
109
+ try:
110
+ return self._request_once(
111
+ method, url, payload, _retried_after_refresh
112
+ )
113
+ except GitHubTimeoutError:
114
+ if attempt == GITHUB_REQUEST_ATTEMPTS:
115
+ logger.warning(
116
+ "GitHub request timed out after %d attempts: %s %s",
117
+ GITHUB_REQUEST_ATTEMPTS,
118
+ method,
119
+ url,
120
+ )
121
+ raise
122
+ logger.warning(
123
+ "GitHub request timed out; retrying (attempt %d/%d): %s %s",
124
+ attempt + 1,
125
+ GITHUB_REQUEST_ATTEMPTS,
126
+ method,
127
+ url,
128
+ )
129
+ raise AssertionError("unreachable")
130
+
131
+ def _request_once(
132
+ self,
133
+ method: str,
134
+ url: str,
135
+ payload: dict[str, Any] | None = None,
136
+ _retried_after_refresh: bool = False,
99
137
  ) -> dict[str, Any]:
100
138
  headers = {
101
139
  "Accept": "application/vnd.github+json",
@@ -122,8 +160,10 @@ class GitHubService:
122
160
  return result
123
161
  except urllib.error.HTTPError as exc:
124
162
  response_body = exc.read().decode("utf-8", errors="replace")
163
+ parsed_response: Any = None
125
164
  try:
126
- response_body = repr(self._safe_response(json.loads(response_body)))
165
+ parsed_response = json.loads(response_body)
166
+ response_body = repr(self._safe_response(parsed_response))
127
167
  except json.JSONDecodeError:
128
168
  response_body = response_body[:1000]
129
169
  logger.debug(
@@ -133,6 +173,10 @@ class GitHubService:
133
173
  exc.code,
134
174
  response_body,
135
175
  )
176
+ if exc.code == 422 and isinstance(parsed_response, dict):
177
+ message = parsed_response.get("message")
178
+ if isinstance(message, str) and "timed out" in message.lower():
179
+ raise GitHubTimeoutError("GitHub request timed out") from exc
136
180
  if exc.code == 404:
137
181
  raise GitHubError("GitHub resource was not found") from exc
138
182
  if exc.code == 401:
@@ -158,6 +202,11 @@ class GitHubService:
158
202
  raise GitHubError(f"GitHub API request failed ({exc.code})") from exc
159
203
  except (urllib.error.URLError, TimeoutError) as exc:
160
204
  logger.debug("GitHub request failed: %s %s error=%r", method, url, exc)
205
+ if isinstance(exc, TimeoutError) or (
206
+ isinstance(exc, urllib.error.URLError)
207
+ and isinstance(exc.reason, TimeoutError)
208
+ ):
209
+ raise GitHubTimeoutError("GitHub request timed out") from exc
161
210
  raise GitHubError("Could not connect to GitHub") from exc
162
211
 
163
212
  def device_code(self, client_id: str, scope: str = "repo offline_access") -> DeviceCode:
@@ -349,8 +398,11 @@ class GitHubService:
349
398
  )
350
399
 
351
400
  def create_blob(self, owner: str, name: str, content: bytes) -> str:
401
+ """Create a Git blob."""
352
402
  encoded = base64.b64encode(content).decode("ascii")
353
- return str(self._request("POST", f"{self.api}/repos/{owner}/{name}/git/blobs", {"content": encoded, "encoding": "base64"})["sha"])
403
+ url = f"{self.api}/repos/{owner}/{name}/git/blobs"
404
+ payload = {"content": encoded, "encoding": "base64"}
405
+ return str(self._request("POST", url, payload)["sha"])
354
406
 
355
407
  def create_tree(self, owner: str, name: str, entries: list[dict[str, str]]) -> str:
356
408
  return str(self._request("POST", f"{self.api}/repos/{owner}/{name}/git/trees", {"tree": entries})["sha"])
@@ -0,0 +1,75 @@
1
+ """Reusable crop and rotation helpers for processing recipes."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import logging
6
+ import math
7
+ from pathlib import Path
8
+ from typing import Any
9
+
10
+ from starbash.fits import read_dimensions
11
+ from starbash.tool import tools
12
+
13
+ siril = tools["siril"]
14
+
15
+ # These globals are populated by recipe scripts before calling ``crop_files``.
16
+ context: dict[str, Any] = {}
17
+ logger: logging.Logger = logging.getLogger(__name__)
18
+
19
+
20
+ def crop_rectangle(width: int, height: int, crop_percent: int = 90) -> tuple[int, int, int, int]:
21
+ """Return a centered crop rectangle retaining ``crop_percent`` of an image."""
22
+ if width <= 0 or height <= 0:
23
+ raise ValueError(f"Image dimensions must be positive, got {width}x{height}")
24
+ if isinstance(crop_percent, bool) or not isinstance(crop_percent, int):
25
+ raise ValueError(f"crop_percent must be an integer, got {crop_percent!r}")
26
+ if not 1 <= crop_percent <= 100:
27
+ raise ValueError(f"crop_percent must be between 1 and 100, got {crop_percent}")
28
+
29
+ crop_width = width * crop_percent // 100
30
+ crop_height = height * crop_percent // 100
31
+ if crop_width < 1 or crop_height < 1:
32
+ raise ValueError(
33
+ f"crop_percent={crop_percent} produces an empty crop for {width}x{height}"
34
+ )
35
+ crop_x = (width - crop_width) // 2
36
+ crop_y = (height - crop_height) // 2
37
+ return crop_x, crop_y, crop_width, crop_height
38
+
39
+
40
+ def crop_files(
41
+ input_paths: list[Path],
42
+ output_paths: list[Path],
43
+ crop_percent: int = 90,
44
+ rotate_deg: int | float = 0,
45
+ ) -> None:
46
+ """Crop and rotate each input FITS file into its corresponding output."""
47
+ if not input_paths or not output_paths:
48
+ raise ValueError("crop_files requires at least one input and output path")
49
+ if len(input_paths) != len(output_paths):
50
+ raise ValueError("crop_files requires equal numbers of input and output paths")
51
+ if isinstance(rotate_deg, bool) or not isinstance(rotate_deg, (int, float)):
52
+ raise ValueError(f"rotate_deg must be numeric, got {rotate_deg!r}")
53
+ if not math.isfinite(rotate_deg):
54
+ raise ValueError(f"rotate_deg must be finite, got {rotate_deg!r}")
55
+
56
+ commands: list[str] = []
57
+ for input_path, output_path in zip(input_paths, output_paths, strict=True):
58
+ width, height = read_dimensions(input_path)
59
+ crop_x, crop_y, crop_width, crop_height = crop_rectangle(
60
+ width, height, crop_percent
61
+ )
62
+ commands.extend(
63
+ [
64
+ f'load "{input_path}"',
65
+ f"crop {crop_x} {crop_y} {crop_width} {crop_height}",
66
+ ]
67
+ )
68
+ if rotate_deg != 0:
69
+ commands.append(f"rotate {rotate_deg:g} -interp=lanczos4")
70
+ commands.append(f'save "{output_path}"')
71
+
72
+ process_dir = context.get("process_dir")
73
+ if not process_dir:
74
+ raise ValueError("crop_files requires context['process_dir']")
75
+ siril.run("\n".join(commands), context=context, cwd=process_dir)
@@ -7,7 +7,6 @@ from pathlib import Path
7
7
  from typing import Any
8
8
 
9
9
  from starbash.exception import NotEnoughFilesError
10
- from starbash.fits import read_dimensions
11
10
  from starbash.siril.import_registration import parse_siril_conversion, parse_siril_seq
12
11
  from starbash.tool import tools
13
12
 
@@ -83,36 +82,6 @@ def fix_sequence_name(path: str) -> str:
83
82
  return base
84
83
 
85
84
 
86
- def _crop_rectangle(width: int, height: int, crop_percent: int = 90) -> tuple[int, int, int, int]:
87
- """Calculate a centered crop rectangle for an image."""
88
- if width <= 0 or height <= 0:
89
- raise ValueError(f"Image dimensions must be positive, got {width}x{height}")
90
- if not 0 < crop_percent <= 100:
91
- raise ValueError(f"crop_percent must be between 1 and 100, got {crop_percent}")
92
-
93
- crop_width = width * crop_percent // 100
94
- crop_height = height * crop_percent // 100
95
- crop_x = (width - crop_width) // 2
96
- crop_y = (height - crop_height) // 2
97
- return crop_x, crop_y, crop_width, crop_height
98
-
99
-
100
- def _crop_final_files(final_paths: list[Path]) -> None:
101
- """Crop final OSC outputs in place using Siril's image crop command."""
102
- commands = ""
103
- for final_path in final_paths:
104
- width, height = read_dimensions(final_path)
105
- crop_x, crop_y, crop_width, crop_height = _crop_rectangle(width, height)
106
- commands += f"""
107
- load "{final_path}"
108
- crop {crop_x} {crop_y} {crop_width} {crop_height}
109
- save "{final_path}"
110
- """
111
-
112
- if commands:
113
- siril.run(commands, context=context, cwd=context["process_dir"])
114
-
115
-
116
85
  def make_stacked(inputs_to_use: list[Any], variant: str | None, output_file: str) -> None:
117
86
  """
118
87
  Registers and stacks all pre-processed light frames for a given filter configuration
@@ -210,12 +179,9 @@ def make_renormalize(channel_num: int) -> None:
210
179
  os.makedirs(results_dir, exist_ok=True)
211
180
 
212
181
  commands = ""
213
- final_paths: list[Path] = []
214
-
215
182
  if channel_num == 1:
216
183
  # Only one channel - just copy it - eventually we'll add other metadata
217
184
  final_path = f"{results_dir}/stacked.fits"
218
- final_paths.append(Path(final_path))
219
185
  commands += f"""
220
186
  load results_00001
221
187
  save "{final_path}"
@@ -231,7 +197,6 @@ def make_renormalize(channel_num: int) -> None:
231
197
 
232
198
  ha_final_path = f"{results_dir}/stacked_Ha.fits"
233
199
  oiii_final_path = f"{results_dir}/stacked_OIII.fits"
234
- final_paths.extend([Path(ha_final_path), Path(oiii_final_path)])
235
200
 
236
201
  # Pixel math formula for renormalization.
237
202
  # It matches the median and spread (MAD) of a channel to a reference channel (Ha).
@@ -258,7 +223,6 @@ def make_renormalize(channel_num: int) -> None:
258
223
  logger.info("Doing renormalisation of extra Sii channel")
259
224
 
260
225
  sii_final_path = f"{results_dir}/stacked_Sii.fits"
261
- final_paths.append(Path(sii_final_path))
262
226
  r_sii = f"r_{sii_base}"
263
227
  pm_sii = f'"${r_sii}$*mad(${r_ha}$)/mad(${r_sii}$)-mad(${r_ha}$)/mad(${r_sii}$)*median(${r_sii}$)+median(${r_ha}$)"'
264
228
  commands += f"""
@@ -268,7 +232,6 @@ def make_renormalize(channel_num: int) -> None:
268
232
  """
269
233
 
270
234
  siril.run(commands, context=context, cwd=context["process_dir"])
271
- _crop_final_files(final_paths)
272
235
  logger.info(f"Saved final renormalized images to {results_dir}")
273
236
 
274
237
 
@@ -0,0 +1,3 @@
1
+ source "https://rubygems.org"
2
+ gem "github-pages", group: :jekyll_plugins
3
+ gem 'jekyll-seo-tag'
@@ -0,0 +1,8 @@
1
+ title: Starbash Images
2
+ markdown: kramdown
3
+ permalink: pretty
4
+ timezone: UTC
5
+ theme: jekyll-theme-midnight
6
+ baseurl: /starbash-public
7
+ plugins:
8
+ - jekyll-seo-tag
@@ -4,6 +4,7 @@
4
4
  <head>
5
5
  <meta charset="utf-8">
6
6
  <meta http-equiv="X-UA-Compatible" content="IE=edge">
7
+ <link rel="icon" href="{{ '/favicon.ico' | relative_url }}" type="image/x-icon">
7
8
 
8
9
  {% seo %}
9
10
  <link rel="stylesheet" href="{{ '/assets/css/style.css?v=' | append: site.github.build_revision | relative_url }}">
@@ -31,7 +32,7 @@
31
32
  <span style="display: block; flex: 1 1 50%; min-width: 200px;"><a
32
33
  href="https://github.com/geeksville/starbash">Starbash</a>
33
34
  is a young project, and we would love to
34
- improve it with your help. Please try it, and we can work together on via github issues.
35
+ improve it with your help. Please try it, and we can work together via github issues.
35
36
  </span>
36
37
  </div>
37
38
  </div><!-- end header -->
@@ -1,6 +1,8 @@
1
1
  ---
2
2
  layout: default
3
- title: "{{ target.name }}"
3
+ title: {{ (target.name ~ ' by ' ~ (github_username | default('Starbash', true))) | tojson }}
4
+ description: {{ description | default('Processed Starbash target.', true) | tojson }}
5
+ {% if image is defined and image %}image: {{ image | tojson }}{% endif %}
4
6
  ---
5
7
 
6
8
  # {{ target.name }}
@@ -1,7 +1,6 @@
1
-
2
-
3
1
  import configparser
4
2
  import logging
3
+ import shutil
5
4
  from pathlib import Path
6
5
 
7
6
  from platformdirs import PlatformDirs
@@ -25,10 +24,11 @@ class StarnetTool(SirilTool):
25
24
  return Path(PlatformDirs("siril").user_config_dir)
26
25
 
27
26
  def _starnet_configured(self) -> bool:
28
- """True if any Siril config file has a non-empty ``starnet_exe`` in ``[core]``."""
27
+ """Ensure Siril has a usable ``starnet_exe`` and report whether it is configured."""
29
28
  config_dir = self._siril_config_dir()
30
29
  # Siril versions its config file (e.g. config.1.4.ini); check whichever exist.
31
- for ini_path in sorted(config_dir.glob("config.*.ini")):
30
+ ini_paths = sorted(config_dir.glob("config.*.ini"))
31
+ for ini_path in ini_paths:
32
32
  parser = configparser.ConfigParser()
33
33
  try:
34
34
  parser.read(ini_path)
@@ -36,6 +36,27 @@ class StarnetTool(SirilTool):
36
36
  continue
37
37
  if parser.get("core", "starnet_exe", fallback="").strip():
38
38
  return True
39
+
40
+ starnet_path = shutil.which("starnet2")
41
+ if starnet_path and ini_paths:
42
+ ini_path = ini_paths[-1]
43
+ parser = configparser.ConfigParser()
44
+ try:
45
+ parser.read(ini_path)
46
+ if not parser.has_section("core"):
47
+ parser.add_section("core")
48
+ parser.set("core", "starnet_exe", str(Path(starnet_path).resolve()))
49
+ with ini_path.open("w", encoding="utf-8") as config_file:
50
+ parser.write(config_file)
51
+ except (OSError, configparser.Error) as exc:
52
+ logger.warning("Unable to add starnet2 to Siril config %s: %s", ini_path, exc)
53
+ else:
54
+ logger.warning(
55
+ "Added starnet2 at %s to the Siril config file %s",
56
+ Path(starnet_path).resolve(),
57
+ ini_path,
58
+ )
59
+ return True
39
60
  return False
40
61
 
41
62
  @property
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes