starbash 0.1.1__py3-none-any.whl → 0.1.3__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.
Potentially problematic release.
This version of starbash might be problematic. Click here for more details.
- starbash/app.py +82 -8
- starbash/commands/repo.py +86 -21
- starbash/commands/selection.py +21 -1
- starbash/database.py +141 -20
- starbash/defaults/starbash.toml +0 -32
- starbash/main.py +7 -1
- starbash/repo/manager.py +5 -0
- starbash/selection.py +36 -0
- starbash/templates/userconfig.toml +32 -0
- starbash-0.1.3.dist-info/METADATA +114 -0
- starbash-0.1.3.dist-info/RECORD +24 -0
- starbash-0.1.1.dist-info/METADATA +0 -96
- starbash-0.1.1.dist-info/RECORD +0 -24
- {starbash-0.1.1.dist-info → starbash-0.1.3.dist-info}/WHEEL +0 -0
- {starbash-0.1.1.dist-info → starbash-0.1.3.dist-info}/entry_points.txt +0 -0
- {starbash-0.1.1.dist-info → starbash-0.1.3.dist-info}/licenses/LICENSE +0 -0
starbash/app.py
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from importlib import resources
|
|
3
3
|
from pathlib import Path
|
|
4
|
-
|
|
4
|
+
import typer
|
|
5
5
|
import tomlkit
|
|
6
6
|
from tomlkit.toml_file import TOMLFile
|
|
7
7
|
import glob
|
|
@@ -10,6 +10,7 @@ from astropy.io import fits
|
|
|
10
10
|
import itertools
|
|
11
11
|
from rich.progress import track
|
|
12
12
|
from rich.logging import RichHandler
|
|
13
|
+
|
|
13
14
|
from starbash.database import Database
|
|
14
15
|
from starbash.repo.manager import Repo
|
|
15
16
|
from starbash.tool import Tool
|
|
@@ -117,7 +118,8 @@ class Starbash:
|
|
|
117
118
|
|
|
118
119
|
def __exit__(self, exc_type, exc, tb) -> bool:
|
|
119
120
|
handled = False
|
|
120
|
-
|
|
121
|
+
# Don't suppress typer.Exit - it's used for controlled exit codes
|
|
122
|
+
if exc and not isinstance(exc, typer.Exit):
|
|
121
123
|
handled = analytics_exception(exc)
|
|
122
124
|
self.close()
|
|
123
125
|
return handled
|
|
@@ -128,11 +130,12 @@ class Starbash:
|
|
|
128
130
|
date = header.get(Database.DATE_OBS_KEY)
|
|
129
131
|
if not date or not image_type:
|
|
130
132
|
logging.warning(
|
|
131
|
-
"Image %s missing
|
|
133
|
+
"Image %s missing either DATE-OBS or IMAGETYP FITS header, skipping...",
|
|
132
134
|
f,
|
|
133
135
|
)
|
|
134
136
|
else:
|
|
135
137
|
exptime = header.get(Database.EXPTIME_KEY, 0)
|
|
138
|
+
telescop = header.get(Database.TELESCOP_KEY, "unspecified")
|
|
136
139
|
new = {
|
|
137
140
|
Database.FILTER_KEY: filter,
|
|
138
141
|
Database.START_KEY: date,
|
|
@@ -142,6 +145,7 @@ class Starbash:
|
|
|
142
145
|
Database.NUM_IMAGES_KEY: 1,
|
|
143
146
|
Database.EXPTIME_TOTAL_KEY: exptime,
|
|
144
147
|
Database.OBJECT_KEY: header.get(Database.OBJECT_KEY, "unspecified"),
|
|
148
|
+
Database.TELESCOP_KEY: telescop,
|
|
145
149
|
}
|
|
146
150
|
session = self.db.get_session(new)
|
|
147
151
|
self.db.upsert_session(new, existing=session)
|
|
@@ -156,16 +160,86 @@ class Starbash:
|
|
|
156
160
|
conditions = self.selection.get_query_conditions()
|
|
157
161
|
return self.db.search_session(conditions)
|
|
158
162
|
|
|
163
|
+
def get_session_images(self, session_id: int) -> list[dict[str, Any]]:
|
|
164
|
+
"""
|
|
165
|
+
Get all images belonging to a specific session.
|
|
166
|
+
|
|
167
|
+
Sessions are defined by a unique combination of filter, imagetyp (image type),
|
|
168
|
+
object (target name), telescope, and date range. This method queries the images
|
|
169
|
+
table for all images matching the session's criteria in a single database query.
|
|
170
|
+
|
|
171
|
+
Args:
|
|
172
|
+
session_id: The database ID of the session
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
List of image records (dictionaries with path, metadata, etc.)
|
|
176
|
+
Returns empty list if session not found or has no images.
|
|
177
|
+
|
|
178
|
+
Raises:
|
|
179
|
+
ValueError: If session_id is not found in the database
|
|
180
|
+
"""
|
|
181
|
+
# First get the session details
|
|
182
|
+
session = self.db.get_session_by_id(session_id)
|
|
183
|
+
if session is None:
|
|
184
|
+
raise ValueError(f"Session with id {session_id} not found")
|
|
185
|
+
|
|
186
|
+
# Query images that match ALL session criteria including date range
|
|
187
|
+
conditions = {
|
|
188
|
+
Database.FILTER_KEY: session[Database.FILTER_KEY],
|
|
189
|
+
Database.IMAGETYP_KEY: session[Database.IMAGETYP_KEY],
|
|
190
|
+
Database.OBJECT_KEY: session[Database.OBJECT_KEY],
|
|
191
|
+
Database.TELESCOP_KEY: session[Database.TELESCOP_KEY],
|
|
192
|
+
"date_start": session[Database.START_KEY],
|
|
193
|
+
"date_end": session[Database.END_KEY],
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
# Single query with all conditions
|
|
197
|
+
images = self.db.search_image(conditions)
|
|
198
|
+
return images if images else []
|
|
199
|
+
|
|
200
|
+
def remove_repo_ref(self, url: str) -> None:
|
|
201
|
+
"""
|
|
202
|
+
Remove a repository reference from the user configuration.
|
|
203
|
+
|
|
204
|
+
Args:
|
|
205
|
+
url: The repository URL to remove (e.g., 'file:///path/to/repo')
|
|
206
|
+
|
|
207
|
+
Raises:
|
|
208
|
+
ValueError: If the repository URL is not found in user configuration
|
|
209
|
+
"""
|
|
210
|
+
# Get the repo-ref list from user config
|
|
211
|
+
repo_refs = self.user_repo.config.get("repo-ref")
|
|
212
|
+
|
|
213
|
+
if not repo_refs:
|
|
214
|
+
raise ValueError(f"No repository references found in user configuration.")
|
|
215
|
+
|
|
216
|
+
# Find and remove the matching repo-ref
|
|
217
|
+
found = False
|
|
218
|
+
refs_copy = [r for r in repo_refs] # Make a copy to iterate
|
|
219
|
+
for ref in refs_copy:
|
|
220
|
+
ref_dir = ref.get("dir", "")
|
|
221
|
+
# Match by converting to file:// URL format if needed
|
|
222
|
+
if ref_dir == url or f"file://{ref_dir}" == url:
|
|
223
|
+
repo_refs.remove(ref)
|
|
224
|
+
found = True
|
|
225
|
+
break
|
|
226
|
+
|
|
227
|
+
if not found:
|
|
228
|
+
raise ValueError(f"Repository '{url}' not found in user configuration.")
|
|
229
|
+
|
|
230
|
+
# Write the updated config
|
|
231
|
+
self.user_repo.write_config()
|
|
232
|
+
|
|
159
233
|
def reindex_repo(self, repo: Repo, force: bool = False):
|
|
160
234
|
"""Reindex all repositories managed by the RepoManager."""
|
|
161
235
|
# FIXME, add a method to get just the repos that contain images
|
|
162
236
|
if repo.is_scheme("file") and repo.kind != "recipe":
|
|
163
237
|
logging.debug("Reindexing %s...", repo.url)
|
|
164
238
|
|
|
239
|
+
whitelist = None
|
|
165
240
|
config = self.repo_manager.merged.get("config")
|
|
166
|
-
if
|
|
167
|
-
|
|
168
|
-
whitelist = config["fits-whitelist"]
|
|
241
|
+
if config:
|
|
242
|
+
whitelist = config.get("fits-whitelist", None)
|
|
169
243
|
|
|
170
244
|
path = repo.get_path()
|
|
171
245
|
if not path:
|
|
@@ -191,7 +265,7 @@ class Starbash:
|
|
|
191
265
|
items = header.items()
|
|
192
266
|
headers = {}
|
|
193
267
|
for key, value in items:
|
|
194
|
-
if key in whitelist:
|
|
268
|
+
if (not whitelist) or (key in whitelist):
|
|
195
269
|
headers[key] = value
|
|
196
270
|
logging.debug("Headers for %s: %s", f, headers)
|
|
197
271
|
headers["path"] = str(f)
|
|
@@ -207,7 +281,7 @@ class Starbash:
|
|
|
207
281
|
|
|
208
282
|
def reindex_repos(self, force: bool = False):
|
|
209
283
|
"""Reindex all repositories managed by the RepoManager."""
|
|
210
|
-
logging.
|
|
284
|
+
logging.debug("Reindexing all repositories...")
|
|
211
285
|
|
|
212
286
|
for repo in track(self.repo_manager.repos, description="Reindexing repos..."):
|
|
213
287
|
self.reindex_repo(repo, force=force)
|
starbash/commands/repo.py
CHANGED
|
@@ -4,7 +4,33 @@ from typing_extensions import Annotated
|
|
|
4
4
|
from starbash.app import Starbash
|
|
5
5
|
from starbash import console
|
|
6
6
|
|
|
7
|
-
app = typer.Typer()
|
|
7
|
+
app = typer.Typer(invoke_without_command=True)
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@app.callback()
|
|
11
|
+
def main(
|
|
12
|
+
ctx: typer.Context,
|
|
13
|
+
verbose: bool = typer.Option(
|
|
14
|
+
False, "--verbose", "-v", help="Show all repos including system repos"
|
|
15
|
+
),
|
|
16
|
+
):
|
|
17
|
+
"""
|
|
18
|
+
Manage repositories.
|
|
19
|
+
|
|
20
|
+
When called without a subcommand, lists all repositories.
|
|
21
|
+
Use --verbose to show all repos including system/recipe repos.
|
|
22
|
+
"""
|
|
23
|
+
# If no subcommand is invoked, run the list behavior
|
|
24
|
+
if ctx.invoked_subcommand is None:
|
|
25
|
+
with Starbash("repo-list") as sb:
|
|
26
|
+
repos = sb.repo_manager.repos if verbose else sb.repo_manager.regular_repos
|
|
27
|
+
for i, repo in enumerate(repos):
|
|
28
|
+
if verbose:
|
|
29
|
+
# No numbers for verbose mode (system repos can't be removed)
|
|
30
|
+
console.print(f"{ repo.url } (kind={ repo.kind})")
|
|
31
|
+
else:
|
|
32
|
+
# Show numbers for user repos (can be removed later)
|
|
33
|
+
console.print(f"{ i + 1:2}: { repo.url } (kind={ repo.kind})")
|
|
8
34
|
|
|
9
35
|
|
|
10
36
|
@app.command()
|
|
@@ -22,46 +48,85 @@ def add(path: str):
|
|
|
22
48
|
|
|
23
49
|
|
|
24
50
|
@app.command()
|
|
25
|
-
def remove(
|
|
51
|
+
def remove(reponum: str):
|
|
26
52
|
"""
|
|
27
|
-
Remove a repository by
|
|
53
|
+
Remove a repository by number (from list).
|
|
54
|
+
Use 'starbash repo' to see the repository numbers.
|
|
28
55
|
"""
|
|
29
56
|
with Starbash("repo-remove") as sb:
|
|
30
|
-
|
|
57
|
+
try:
|
|
58
|
+
# Parse the repo number (1-indexed)
|
|
59
|
+
repo_index = int(reponum) - 1
|
|
31
60
|
|
|
61
|
+
# Get only the regular (user-visible) repos
|
|
62
|
+
regular_repos = sb.repo_manager.regular_repos
|
|
32
63
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
64
|
+
if repo_index < 0 or repo_index >= len(regular_repos):
|
|
65
|
+
console.print(
|
|
66
|
+
f"[red]Error: Repository number {reponum} is out of range. Valid range: 1-{len(regular_repos)}[/red]"
|
|
67
|
+
)
|
|
68
|
+
raise typer.Exit(code=1)
|
|
69
|
+
|
|
70
|
+
# Get the repo to remove
|
|
71
|
+
repo_to_remove = regular_repos[repo_index]
|
|
72
|
+
repo_url = repo_to_remove.url
|
|
73
|
+
|
|
74
|
+
# Remove the repo reference from user config
|
|
75
|
+
sb.remove_repo_ref(repo_url)
|
|
76
|
+
console.print(f"[green]Removed repository: {repo_url}[/green]")
|
|
77
|
+
|
|
78
|
+
except ValueError:
|
|
79
|
+
console.print(
|
|
80
|
+
f"[red]Error: '{reponum}' is not a valid repository number. Please use a number from 'repo list'.[/red]"
|
|
81
|
+
)
|
|
82
|
+
raise typer.Exit(code=1)
|
|
41
83
|
|
|
42
84
|
|
|
43
85
|
@app.command()
|
|
44
86
|
def reindex(
|
|
45
|
-
|
|
87
|
+
reponum: Annotated[
|
|
46
88
|
str | None,
|
|
47
|
-
typer.Argument(
|
|
48
|
-
help="The repository name or number, if not specified reindex all."
|
|
49
|
-
),
|
|
89
|
+
typer.Argument(help="The repository number, if not specified reindex all."),
|
|
50
90
|
] = None,
|
|
51
91
|
force: bool = typer.Option(
|
|
52
92
|
default=False, help="Reread FITS headers, even if they are already indexed."
|
|
53
93
|
),
|
|
54
94
|
):
|
|
55
95
|
"""
|
|
56
|
-
Reindex
|
|
57
|
-
If no
|
|
96
|
+
Reindex a repository by number.
|
|
97
|
+
If no number is given, reindex all repositories.
|
|
98
|
+
Use 'starbash repo' to see the repository numbers.
|
|
58
99
|
"""
|
|
59
100
|
with Starbash("repo-reindex") as sb:
|
|
60
|
-
if
|
|
61
|
-
console.print("Reindexing all repositories...")
|
|
101
|
+
if reponum is None:
|
|
62
102
|
sb.reindex_repos(force=force)
|
|
63
103
|
else:
|
|
64
|
-
|
|
104
|
+
try:
|
|
105
|
+
# Parse the repo number (1-indexed)
|
|
106
|
+
repo_index = int(reponum) - 1
|
|
107
|
+
|
|
108
|
+
# Get only the regular (user-visible) repos
|
|
109
|
+
regular_repos = sb.repo_manager.regular_repos
|
|
110
|
+
|
|
111
|
+
if repo_index < 0 or repo_index >= len(regular_repos):
|
|
112
|
+
console.print(
|
|
113
|
+
f"[red]Error: Repository number {reponum} is out of range. Valid range: 1-{len(regular_repos)}[/red]"
|
|
114
|
+
)
|
|
115
|
+
raise typer.Exit(code=1)
|
|
116
|
+
|
|
117
|
+
# Get the repo to reindex
|
|
118
|
+
repo_to_reindex = regular_repos[repo_index]
|
|
119
|
+
console.print(f"Reindexing repository: {repo_to_reindex.url}")
|
|
120
|
+
sb.reindex_repo(repo_to_reindex, force=force)
|
|
121
|
+
console.print(
|
|
122
|
+
f"[green]Successfully reindexed repository {reponum}[/green]"
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
except ValueError:
|
|
126
|
+
console.print(
|
|
127
|
+
f"[red]Error: '{reponum}' is not a valid repository number. Please use a number from 'starbash repo'.[/red]"
|
|
128
|
+
)
|
|
129
|
+
raise typer.Exit(code=1)
|
|
65
130
|
|
|
66
131
|
|
|
67
132
|
if __name__ == "__main__":
|
starbash/commands/selection.py
CHANGED
|
@@ -31,11 +31,31 @@ def target(
|
|
|
31
31
|
with Starbash("selection-target") as sb:
|
|
32
32
|
# For now, replace existing targets with this one
|
|
33
33
|
# In the future, we could support adding multiple targets
|
|
34
|
-
sb.selection.
|
|
34
|
+
sb.selection.targets = []
|
|
35
35
|
sb.selection.add_target(target_name)
|
|
36
36
|
console.print(f"[green]Selection limited to target: {target_name}[/green]")
|
|
37
37
|
|
|
38
38
|
|
|
39
|
+
@app.command()
|
|
40
|
+
def telescope(
|
|
41
|
+
telescope_name: Annotated[
|
|
42
|
+
str,
|
|
43
|
+
typer.Argument(
|
|
44
|
+
help="Telescope name to add to the selection (e.g., 'Vespera', 'EdgeHD 8')"
|
|
45
|
+
),
|
|
46
|
+
],
|
|
47
|
+
):
|
|
48
|
+
"""Limit the current selection to only the named telescope."""
|
|
49
|
+
with Starbash("selection-telescope") as sb:
|
|
50
|
+
# For now, replace existing telescopes with this one
|
|
51
|
+
# In the future, we could support adding multiple telescopes
|
|
52
|
+
sb.selection.telescopes = []
|
|
53
|
+
sb.selection.add_telescope(telescope_name)
|
|
54
|
+
console.print(
|
|
55
|
+
f"[green]Selection limited to telescope: {telescope_name}[/green]"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
|
|
39
59
|
@app.command()
|
|
40
60
|
def date(
|
|
41
61
|
operation: Annotated[
|
starbash/database.py
CHANGED
|
@@ -15,6 +15,9 @@ class Database:
|
|
|
15
15
|
|
|
16
16
|
Stores data under the OS-specific user data directory using platformdirs.
|
|
17
17
|
Provides an `images` table for FITS metadata and basic helpers.
|
|
18
|
+
|
|
19
|
+
The images table stores DATE-OBS and DATE as indexed SQL columns for
|
|
20
|
+
efficient date-based queries, while other FITS metadata is stored in JSON.
|
|
18
21
|
"""
|
|
19
22
|
|
|
20
23
|
EXPTIME_KEY = "EXPTIME"
|
|
@@ -24,9 +27,11 @@ class Database:
|
|
|
24
27
|
NUM_IMAGES_KEY = "num-images"
|
|
25
28
|
EXPTIME_TOTAL_KEY = "exptime-total"
|
|
26
29
|
DATE_OBS_KEY = "DATE-OBS"
|
|
30
|
+
DATE_KEY = "DATE"
|
|
27
31
|
IMAGE_DOC_KEY = "image-doc"
|
|
28
32
|
IMAGETYP_KEY = "IMAGETYP"
|
|
29
33
|
OBJECT_KEY = "OBJECT"
|
|
34
|
+
TELESCOP_KEY = "TELESCOP"
|
|
30
35
|
|
|
31
36
|
def __init__(
|
|
32
37
|
self,
|
|
@@ -52,12 +57,14 @@ class Database:
|
|
|
52
57
|
"""Create the images and sessions tables if they don't exist."""
|
|
53
58
|
cursor = self._db.cursor()
|
|
54
59
|
|
|
55
|
-
# Create images table
|
|
60
|
+
# Create images table with DATE-OBS and DATE as indexed columns
|
|
56
61
|
cursor.execute(
|
|
57
62
|
"""
|
|
58
63
|
CREATE TABLE IF NOT EXISTS images (
|
|
59
64
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
60
65
|
path TEXT UNIQUE NOT NULL,
|
|
66
|
+
date_obs TEXT,
|
|
67
|
+
date TEXT,
|
|
61
68
|
metadata TEXT NOT NULL
|
|
62
69
|
)
|
|
63
70
|
"""
|
|
@@ -70,6 +77,20 @@ class Database:
|
|
|
70
77
|
"""
|
|
71
78
|
)
|
|
72
79
|
|
|
80
|
+
# Create index on date_obs for efficient date range queries
|
|
81
|
+
cursor.execute(
|
|
82
|
+
"""
|
|
83
|
+
CREATE INDEX IF NOT EXISTS idx_images_date_obs ON images(date_obs)
|
|
84
|
+
"""
|
|
85
|
+
)
|
|
86
|
+
|
|
87
|
+
# Create index on date for queries using DATE field
|
|
88
|
+
cursor.execute(
|
|
89
|
+
"""
|
|
90
|
+
CREATE INDEX IF NOT EXISTS idx_images_date ON images(date)
|
|
91
|
+
"""
|
|
92
|
+
)
|
|
93
|
+
|
|
73
94
|
# Create sessions table
|
|
74
95
|
cursor.execute(
|
|
75
96
|
"""
|
|
@@ -80,6 +101,7 @@ class Database:
|
|
|
80
101
|
filter TEXT NOT NULL,
|
|
81
102
|
imagetyp TEXT NOT NULL,
|
|
82
103
|
object TEXT NOT NULL,
|
|
104
|
+
telescop TEXT NOT NULL,
|
|
83
105
|
num_images INTEGER NOT NULL,
|
|
84
106
|
exptime_total REAL NOT NULL,
|
|
85
107
|
image_doc_id INTEGER
|
|
@@ -91,7 +113,7 @@ class Database:
|
|
|
91
113
|
cursor.execute(
|
|
92
114
|
"""
|
|
93
115
|
CREATE INDEX IF NOT EXISTS idx_sessions_lookup
|
|
94
|
-
ON sessions(filter, imagetyp, object, start, end)
|
|
116
|
+
ON sessions(filter, imagetyp, object, telescop, start, end)
|
|
95
117
|
"""
|
|
96
118
|
)
|
|
97
119
|
|
|
@@ -102,23 +124,31 @@ class Database:
|
|
|
102
124
|
"""Insert or update an image record by unique path.
|
|
103
125
|
|
|
104
126
|
The record must include a 'path' key; other keys are arbitrary FITS metadata.
|
|
127
|
+
DATE-OBS and DATE are extracted and stored as indexed columns for efficient queries.
|
|
105
128
|
Returns the rowid of the inserted/updated record.
|
|
106
129
|
"""
|
|
107
130
|
path = record.get("path")
|
|
108
131
|
if not path:
|
|
109
132
|
raise ValueError("record must include 'path'")
|
|
110
133
|
|
|
111
|
-
#
|
|
134
|
+
# Extract date fields for column storage
|
|
135
|
+
date_obs = record.get(self.DATE_OBS_KEY)
|
|
136
|
+
date = record.get(self.DATE_KEY)
|
|
137
|
+
|
|
138
|
+
# Separate path and date fields from metadata
|
|
112
139
|
metadata = {k: v for k, v in record.items() if k != "path"}
|
|
113
140
|
metadata_json = json.dumps(metadata)
|
|
114
141
|
|
|
115
142
|
cursor = self._db.cursor()
|
|
116
143
|
cursor.execute(
|
|
117
144
|
"""
|
|
118
|
-
INSERT INTO images (path, metadata) VALUES (?, ?)
|
|
119
|
-
ON CONFLICT(path) DO UPDATE SET
|
|
145
|
+
INSERT INTO images (path, date_obs, date, metadata) VALUES (?, ?, ?, ?)
|
|
146
|
+
ON CONFLICT(path) DO UPDATE SET
|
|
147
|
+
date_obs = excluded.date_obs,
|
|
148
|
+
date = excluded.date,
|
|
149
|
+
metadata = excluded.metadata
|
|
120
150
|
""",
|
|
121
|
-
(path, metadata_json),
|
|
151
|
+
(path, date_obs, date, metadata_json),
|
|
122
152
|
)
|
|
123
153
|
|
|
124
154
|
self._db.commit()
|
|
@@ -134,13 +164,38 @@ class Database:
|
|
|
134
164
|
"""Search for images matching the given conditions.
|
|
135
165
|
|
|
136
166
|
Args:
|
|
137
|
-
conditions: Dictionary of metadata key-value pairs to match
|
|
167
|
+
conditions: Dictionary of metadata key-value pairs to match.
|
|
168
|
+
Special keys:
|
|
169
|
+
- 'date_start': Filter images with DATE-OBS >= this date
|
|
170
|
+
- 'date_end': Filter images with DATE-OBS <= this date
|
|
138
171
|
|
|
139
172
|
Returns:
|
|
140
173
|
List of matching image records or None if no matches
|
|
141
174
|
"""
|
|
175
|
+
# Extract special date filter keys (make a copy to avoid modifying caller's dict)
|
|
176
|
+
conditions_copy = dict(conditions)
|
|
177
|
+
date_start = conditions_copy.pop("date_start", None)
|
|
178
|
+
date_end = conditions_copy.pop("date_end", None)
|
|
179
|
+
|
|
180
|
+
# Build SQL query with WHERE clauses for date filtering
|
|
181
|
+
where_clauses = []
|
|
182
|
+
params = []
|
|
183
|
+
|
|
184
|
+
if date_start:
|
|
185
|
+
where_clauses.append("date_obs >= ?")
|
|
186
|
+
params.append(date_start)
|
|
187
|
+
|
|
188
|
+
if date_end:
|
|
189
|
+
where_clauses.append("date_obs <= ?")
|
|
190
|
+
params.append(date_end)
|
|
191
|
+
|
|
192
|
+
# Build the query
|
|
193
|
+
query = "SELECT id, path, date_obs, date, metadata FROM images"
|
|
194
|
+
if where_clauses:
|
|
195
|
+
query += " WHERE " + " AND ".join(where_clauses)
|
|
196
|
+
|
|
142
197
|
cursor = self._db.cursor()
|
|
143
|
-
cursor.execute(
|
|
198
|
+
cursor.execute(query, params)
|
|
144
199
|
|
|
145
200
|
results = []
|
|
146
201
|
for row in cursor.fetchall():
|
|
@@ -148,8 +203,15 @@ class Database:
|
|
|
148
203
|
metadata["path"] = row["path"]
|
|
149
204
|
metadata["id"] = row["id"]
|
|
150
205
|
|
|
151
|
-
#
|
|
152
|
-
|
|
206
|
+
# Add date fields back to metadata for compatibility
|
|
207
|
+
if row["date_obs"]:
|
|
208
|
+
metadata[self.DATE_OBS_KEY] = row["date_obs"]
|
|
209
|
+
if row["date"]:
|
|
210
|
+
metadata[self.DATE_KEY] = row["date"]
|
|
211
|
+
|
|
212
|
+
# Check if remaining conditions match (those stored in JSON metadata)
|
|
213
|
+
match = all(metadata.get(k) == v for k, v in conditions_copy.items())
|
|
214
|
+
|
|
153
215
|
if match:
|
|
154
216
|
results.append(metadata)
|
|
155
217
|
|
|
@@ -175,7 +237,7 @@ class Database:
|
|
|
175
237
|
cursor = self._db.cursor()
|
|
176
238
|
cursor.execute(
|
|
177
239
|
"""
|
|
178
|
-
SELECT id, start, end, filter, imagetyp, object,
|
|
240
|
+
SELECT id, start, end, filter, imagetyp, object, telescop,
|
|
179
241
|
num_images, exptime_total, image_doc_id
|
|
180
242
|
FROM sessions
|
|
181
243
|
"""
|
|
@@ -201,6 +263,7 @@ class Database:
|
|
|
201
263
|
self.FILTER_KEY: row["filter"],
|
|
202
264
|
self.IMAGETYP_KEY: row["imagetyp"],
|
|
203
265
|
self.OBJECT_KEY: row["object"],
|
|
266
|
+
self.TELESCOP_KEY: row["telescop"],
|
|
204
267
|
self.NUM_IMAGES_KEY: row["num_images"],
|
|
205
268
|
self.EXPTIME_TOTAL_KEY: row["exptime_total"],
|
|
206
269
|
self.IMAGE_DOC_KEY: row["image_doc_id"],
|
|
@@ -233,7 +296,10 @@ class Database:
|
|
|
233
296
|
def get_image(self, path: str) -> dict[str, Any] | None:
|
|
234
297
|
"""Get an image record by path."""
|
|
235
298
|
cursor = self._db.cursor()
|
|
236
|
-
cursor.execute(
|
|
299
|
+
cursor.execute(
|
|
300
|
+
"SELECT id, path, date_obs, date, metadata FROM images WHERE path = ?",
|
|
301
|
+
(path,),
|
|
302
|
+
)
|
|
237
303
|
row = cursor.fetchone()
|
|
238
304
|
|
|
239
305
|
if row is None:
|
|
@@ -242,18 +308,32 @@ class Database:
|
|
|
242
308
|
metadata = json.loads(row["metadata"])
|
|
243
309
|
metadata["path"] = row["path"]
|
|
244
310
|
metadata["id"] = row["id"]
|
|
311
|
+
|
|
312
|
+
# Add date fields back to metadata for compatibility
|
|
313
|
+
if row["date_obs"]:
|
|
314
|
+
metadata[self.DATE_OBS_KEY] = row["date_obs"]
|
|
315
|
+
if row["date"]:
|
|
316
|
+
metadata[self.DATE_KEY] = row["date"]
|
|
317
|
+
|
|
245
318
|
return metadata
|
|
246
319
|
|
|
247
320
|
def all_images(self) -> list[dict[str, Any]]:
|
|
248
321
|
"""Return all image records."""
|
|
249
322
|
cursor = self._db.cursor()
|
|
250
|
-
cursor.execute("SELECT id, path, metadata FROM images")
|
|
323
|
+
cursor.execute("SELECT id, path, date_obs, date, metadata FROM images")
|
|
251
324
|
|
|
252
325
|
results = []
|
|
253
326
|
for row in cursor.fetchall():
|
|
254
327
|
metadata = json.loads(row["metadata"])
|
|
255
328
|
metadata["path"] = row["path"]
|
|
256
329
|
metadata["id"] = row["id"]
|
|
330
|
+
|
|
331
|
+
# Add date fields back to metadata for compatibility
|
|
332
|
+
if row["date_obs"]:
|
|
333
|
+
metadata[self.DATE_OBS_KEY] = row["date_obs"]
|
|
334
|
+
if row["date"]:
|
|
335
|
+
metadata[self.DATE_KEY] = row["date"]
|
|
336
|
+
|
|
257
337
|
results.append(metadata)
|
|
258
338
|
|
|
259
339
|
return results
|
|
@@ -263,7 +343,7 @@ class Database:
|
|
|
263
343
|
cursor = self._db.cursor()
|
|
264
344
|
cursor.execute(
|
|
265
345
|
"""
|
|
266
|
-
SELECT id, start, end, filter, imagetyp, object,
|
|
346
|
+
SELECT id, start, end, filter, imagetyp, object, telescop,
|
|
267
347
|
num_images, exptime_total, image_doc_id
|
|
268
348
|
FROM sessions
|
|
269
349
|
"""
|
|
@@ -278,6 +358,7 @@ class Database:
|
|
|
278
358
|
self.FILTER_KEY: row["filter"],
|
|
279
359
|
self.IMAGETYP_KEY: row["imagetyp"],
|
|
280
360
|
self.OBJECT_KEY: row["object"],
|
|
361
|
+
self.TELESCOP_KEY: row["telescop"],
|
|
281
362
|
self.NUM_IMAGES_KEY: row["num_images"],
|
|
282
363
|
self.EXPTIME_TOTAL_KEY: row["exptime_total"],
|
|
283
364
|
self.IMAGE_DOC_KEY: row["image_doc_id"],
|
|
@@ -286,10 +367,47 @@ class Database:
|
|
|
286
367
|
|
|
287
368
|
return results
|
|
288
369
|
|
|
370
|
+
def get_session_by_id(self, session_id: int) -> dict[str, Any] | None:
|
|
371
|
+
"""Get a session record by its ID.
|
|
372
|
+
|
|
373
|
+
Args:
|
|
374
|
+
session_id: The database ID of the session
|
|
375
|
+
|
|
376
|
+
Returns:
|
|
377
|
+
Session record dictionary or None if not found
|
|
378
|
+
"""
|
|
379
|
+
cursor = self._db.cursor()
|
|
380
|
+
cursor.execute(
|
|
381
|
+
"""
|
|
382
|
+
SELECT id, start, end, filter, imagetyp, object, telescop,
|
|
383
|
+
num_images, exptime_total, image_doc_id
|
|
384
|
+
FROM sessions
|
|
385
|
+
WHERE id = ?
|
|
386
|
+
""",
|
|
387
|
+
(session_id,),
|
|
388
|
+
)
|
|
389
|
+
|
|
390
|
+
row = cursor.fetchone()
|
|
391
|
+
if row is None:
|
|
392
|
+
return None
|
|
393
|
+
|
|
394
|
+
return {
|
|
395
|
+
"id": row["id"],
|
|
396
|
+
self.START_KEY: row["start"],
|
|
397
|
+
self.END_KEY: row["end"],
|
|
398
|
+
self.FILTER_KEY: row["filter"],
|
|
399
|
+
self.IMAGETYP_KEY: row["imagetyp"],
|
|
400
|
+
self.OBJECT_KEY: row["object"],
|
|
401
|
+
self.TELESCOP_KEY: row["telescop"],
|
|
402
|
+
self.NUM_IMAGES_KEY: row["num_images"],
|
|
403
|
+
self.EXPTIME_TOTAL_KEY: row["exptime_total"],
|
|
404
|
+
self.IMAGE_DOC_KEY: row["image_doc_id"],
|
|
405
|
+
}
|
|
406
|
+
|
|
289
407
|
def get_session(self, to_find: dict[str, str]) -> dict[str, Any] | None:
|
|
290
408
|
"""Find a session matching the given criteria.
|
|
291
409
|
|
|
292
|
-
Searches for sessions with the same filter, image type, and
|
|
410
|
+
Searches for sessions with the same filter, image type, target, and telescope
|
|
293
411
|
whose start time is within +/- 8 hours of the provided date.
|
|
294
412
|
"""
|
|
295
413
|
date = to_find.get(Database.START_KEY)
|
|
@@ -300,6 +418,7 @@ class Database:
|
|
|
300
418
|
assert filter
|
|
301
419
|
target = to_find.get(Database.OBJECT_KEY)
|
|
302
420
|
assert target
|
|
421
|
+
telescop = to_find.get(Database.TELESCOP_KEY, "unspecified")
|
|
303
422
|
|
|
304
423
|
# Convert the provided ISO8601 date string to a datetime, then
|
|
305
424
|
# search for sessions with the same filter whose start time is
|
|
@@ -314,14 +433,14 @@ class Database:
|
|
|
314
433
|
cursor = self._db.cursor()
|
|
315
434
|
cursor.execute(
|
|
316
435
|
"""
|
|
317
|
-
SELECT id, start, end, filter, imagetyp, object,
|
|
436
|
+
SELECT id, start, end, filter, imagetyp, object, telescop,
|
|
318
437
|
num_images, exptime_total, image_doc_id
|
|
319
438
|
FROM sessions
|
|
320
|
-
WHERE filter = ? AND imagetyp = ? AND object = ?
|
|
439
|
+
WHERE filter = ? AND imagetyp = ? AND object = ? AND telescop = ?
|
|
321
440
|
AND start >= ? AND start <= ?
|
|
322
441
|
LIMIT 1
|
|
323
442
|
""",
|
|
324
|
-
(filter, image_type, target, start_min, start_max),
|
|
443
|
+
(filter, image_type, target, telescop, start_min, start_max),
|
|
325
444
|
)
|
|
326
445
|
|
|
327
446
|
row = cursor.fetchone()
|
|
@@ -335,6 +454,7 @@ class Database:
|
|
|
335
454
|
self.FILTER_KEY: row["filter"],
|
|
336
455
|
self.IMAGETYP_KEY: row["imagetyp"],
|
|
337
456
|
self.OBJECT_KEY: row["object"],
|
|
457
|
+
self.TELESCOP_KEY: row["telescop"],
|
|
338
458
|
self.NUM_IMAGES_KEY: row["num_images"],
|
|
339
459
|
self.EXPTIME_TOTAL_KEY: row["exptime_total"],
|
|
340
460
|
self.IMAGE_DOC_KEY: row["image_doc_id"],
|
|
@@ -376,8 +496,8 @@ class Database:
|
|
|
376
496
|
cursor.execute(
|
|
377
497
|
"""
|
|
378
498
|
INSERT INTO sessions
|
|
379
|
-
(start, end, filter, imagetyp, object, num_images, exptime_total, image_doc_id)
|
|
380
|
-
VALUES (?, ?, ?, ?, ?, ?, ?, ?)
|
|
499
|
+
(start, end, filter, imagetyp, object, telescop, num_images, exptime_total, image_doc_id)
|
|
500
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
381
501
|
""",
|
|
382
502
|
(
|
|
383
503
|
new[Database.START_KEY],
|
|
@@ -385,6 +505,7 @@ class Database:
|
|
|
385
505
|
new[Database.FILTER_KEY],
|
|
386
506
|
new[Database.IMAGETYP_KEY],
|
|
387
507
|
new[Database.OBJECT_KEY],
|
|
508
|
+
new.get(Database.TELESCOP_KEY, "unspecified"),
|
|
388
509
|
new[Database.NUM_IMAGES_KEY],
|
|
389
510
|
new[Database.EXPTIME_TOTAL_KEY],
|
|
390
511
|
new.get(Database.IMAGE_DOC_KEY),
|
starbash/defaults/starbash.toml
CHANGED
|
@@ -47,35 +47,3 @@ dir = "/workspaces/starbash/doc/toml/example/recipe-repo"
|
|
|
47
47
|
# location in the sequence as if they were defined here
|
|
48
48
|
#by-file = "process-osc-dual-duo"
|
|
49
49
|
#by-url = "http:..."
|
|
50
|
-
|
|
51
|
-
[config]
|
|
52
|
-
|
|
53
|
-
# What fits fields should we store in our DB cache
|
|
54
|
-
fits-whitelist = [
|
|
55
|
-
"INSTRUME",
|
|
56
|
-
"FILTER",
|
|
57
|
-
"TELESCOP",
|
|
58
|
-
"IMAGETYP",
|
|
59
|
-
"DATE-OBS",
|
|
60
|
-
"DATE-LOC",
|
|
61
|
-
"DATE",
|
|
62
|
-
"EXPTIME", # Use use this instead of EXPOSURE because it seems like not all apps use EXPOSURE (Siril)
|
|
63
|
-
"FWHEEL",
|
|
64
|
-
"OBJECT",
|
|
65
|
-
"RA", # we ignore the text version OBJCTRA / OBJCTDEC
|
|
66
|
-
"DEC",
|
|
67
|
-
"OBJCTROT",
|
|
68
|
-
"FOCPOS",
|
|
69
|
-
"SITELAT",
|
|
70
|
-
"SITELON",
|
|
71
|
-
"SITEELEV",
|
|
72
|
-
"NAXIS1",
|
|
73
|
-
"NAXIS2",
|
|
74
|
-
"SWCREATE",
|
|
75
|
-
"XBINNING",
|
|
76
|
-
"YBINNING",
|
|
77
|
-
"GAIN",
|
|
78
|
-
"CCD-TEMP",
|
|
79
|
-
"SET-TEMP",
|
|
80
|
-
"AMBTEMP",
|
|
81
|
-
]
|
starbash/main.py
CHANGED
|
@@ -59,6 +59,7 @@ def session():
|
|
|
59
59
|
table.add_column("# images", style="cyan", no_wrap=True)
|
|
60
60
|
table.add_column("Time", style="cyan", no_wrap=True)
|
|
61
61
|
table.add_column("Type/Filter", style="cyan", no_wrap=True)
|
|
62
|
+
table.add_column("Telescope", style="cyan", no_wrap=True)
|
|
62
63
|
table.add_column(
|
|
63
64
|
"About", style="cyan", no_wrap=True
|
|
64
65
|
) # type of frames, filter, target
|
|
@@ -80,6 +81,7 @@ def session():
|
|
|
80
81
|
object = str(sess.get(Database.OBJECT_KEY, "N/A"))
|
|
81
82
|
filter = sess.get(Database.FILTER_KEY, "N/A")
|
|
82
83
|
image_type = str(sess.get(Database.IMAGETYP_KEY, "N/A"))
|
|
84
|
+
telescop = str(sess.get(Database.TELESCOP_KEY, "N/A"))
|
|
83
85
|
|
|
84
86
|
# Format total exposure time as integer seconds
|
|
85
87
|
exptime_raw = str(sess.get(Database.EXPTIME_TOTAL_KEY, "N/A"))
|
|
@@ -100,14 +102,17 @@ def session():
|
|
|
100
102
|
type_str = image_type
|
|
101
103
|
if image_type.upper() == "LIGHT":
|
|
102
104
|
image_type = filter
|
|
103
|
-
|
|
105
|
+
elif image_type.upper() == "FLAT":
|
|
104
106
|
image_type = f"{image_type}/{filter}"
|
|
107
|
+
else: # either bias or dark
|
|
108
|
+
object = "" # Don't show meaningless target
|
|
105
109
|
|
|
106
110
|
table.add_row(
|
|
107
111
|
date,
|
|
108
112
|
str(num_images),
|
|
109
113
|
total_secs,
|
|
110
114
|
image_type,
|
|
115
|
+
telescop,
|
|
111
116
|
object,
|
|
112
117
|
)
|
|
113
118
|
|
|
@@ -119,6 +124,7 @@ def session():
|
|
|
119
124
|
f"[bold]{format_duration(int(total_seconds))}[/bold]",
|
|
120
125
|
"",
|
|
121
126
|
"",
|
|
127
|
+
"",
|
|
122
128
|
)
|
|
123
129
|
|
|
124
130
|
console.print(table)
|
starbash/repo/manager.py
CHANGED
|
@@ -245,6 +245,11 @@ class RepoManager:
|
|
|
245
245
|
# Most users will just want to read from merged
|
|
246
246
|
self.merged = MultiDict()
|
|
247
247
|
|
|
248
|
+
@property
|
|
249
|
+
def regular_repos(self) -> list[Repo]:
|
|
250
|
+
"We exclude certain repo types (preferences, recipe) from the list of repos users care about."
|
|
251
|
+
return [r for r in self.repos if r.kind not in ("preferences", "recipe")]
|
|
252
|
+
|
|
248
253
|
def add_repo(self, url: str) -> Repo:
|
|
249
254
|
logging.debug(f"Adding repo: {url}")
|
|
250
255
|
r = Repo(self, url)
|
starbash/selection.py
CHANGED
|
@@ -17,6 +17,7 @@ class Selection:
|
|
|
17
17
|
- Date ranges
|
|
18
18
|
- Filters
|
|
19
19
|
- Image types
|
|
20
|
+
- Telescope names
|
|
20
21
|
|
|
21
22
|
The selection state is saved to disk and can be used to build database queries.
|
|
22
23
|
"""
|
|
@@ -33,6 +34,7 @@ class Selection:
|
|
|
33
34
|
self.date_end: Optional[str] = None
|
|
34
35
|
self.filters: list[str] = []
|
|
35
36
|
self.image_types: list[str] = []
|
|
37
|
+
self.telescopes: list[str] = []
|
|
36
38
|
|
|
37
39
|
# Load existing state if it exists
|
|
38
40
|
self._load()
|
|
@@ -48,6 +50,7 @@ class Selection:
|
|
|
48
50
|
self.date_end = data.get("date_end")
|
|
49
51
|
self.filters = data.get("filters", [])
|
|
50
52
|
self.image_types = data.get("image_types", [])
|
|
53
|
+
self.telescopes = data.get("telescopes", [])
|
|
51
54
|
logging.debug(f"Loaded selection state from {self.state_file}")
|
|
52
55
|
except Exception as e:
|
|
53
56
|
logging.warning(f"Failed to load selection state: {e}")
|
|
@@ -64,6 +67,7 @@ class Selection:
|
|
|
64
67
|
"date_end": self.date_end,
|
|
65
68
|
"filters": self.filters,
|
|
66
69
|
"image_types": self.image_types,
|
|
70
|
+
"telescopes": self.telescopes,
|
|
67
71
|
}
|
|
68
72
|
|
|
69
73
|
with open(self.state_file, "w") as f:
|
|
@@ -79,6 +83,7 @@ class Selection:
|
|
|
79
83
|
self.date_end = None
|
|
80
84
|
self.filters = []
|
|
81
85
|
self.image_types = []
|
|
86
|
+
self.telescopes = []
|
|
82
87
|
self._save()
|
|
83
88
|
|
|
84
89
|
def add_target(self, target: str) -> None:
|
|
@@ -101,6 +106,26 @@ class Selection:
|
|
|
101
106
|
self.targets.remove(target)
|
|
102
107
|
self._save()
|
|
103
108
|
|
|
109
|
+
def add_telescope(self, telescope: str) -> None:
|
|
110
|
+
"""Add a telescope to the selection.
|
|
111
|
+
|
|
112
|
+
Args:
|
|
113
|
+
telescope: Telescope name to add to the selection
|
|
114
|
+
"""
|
|
115
|
+
if telescope not in self.telescopes:
|
|
116
|
+
self.telescopes.append(telescope)
|
|
117
|
+
self._save()
|
|
118
|
+
|
|
119
|
+
def remove_telescope(self, telescope: str) -> None:
|
|
120
|
+
"""Remove a telescope from the selection.
|
|
121
|
+
|
|
122
|
+
Args:
|
|
123
|
+
telescope: Telescope name to remove from the selection
|
|
124
|
+
"""
|
|
125
|
+
if telescope in self.telescopes:
|
|
126
|
+
self.telescopes.remove(telescope)
|
|
127
|
+
self._save()
|
|
128
|
+
|
|
104
129
|
def set_date_range(
|
|
105
130
|
self, start: Optional[str] = None, end: Optional[str] = None
|
|
106
131
|
) -> None:
|
|
@@ -146,6 +171,7 @@ class Selection:
|
|
|
146
171
|
and self.date_end is None
|
|
147
172
|
and not self.filters
|
|
148
173
|
and not self.image_types
|
|
174
|
+
and not self.telescopes
|
|
149
175
|
)
|
|
150
176
|
|
|
151
177
|
def get_query_conditions(self) -> dict[str, Any]:
|
|
@@ -173,6 +199,13 @@ class Selection:
|
|
|
173
199
|
# TODO: Support multiple filters in queries
|
|
174
200
|
conditions["FILTER"] = self.filters[0] if len(self.filters) == 1 else None
|
|
175
201
|
|
|
202
|
+
if self.telescopes:
|
|
203
|
+
# For now, just use the first telescope
|
|
204
|
+
# TODO: Support multiple telescopes in queries
|
|
205
|
+
conditions["TELESCOP"] = (
|
|
206
|
+
self.telescopes[0] if len(self.telescopes) == 1 else None
|
|
207
|
+
)
|
|
208
|
+
|
|
176
209
|
# Add date range conditions
|
|
177
210
|
if self.date_start:
|
|
178
211
|
conditions["date_start"] = self.date_start
|
|
@@ -198,6 +231,9 @@ class Selection:
|
|
|
198
231
|
if self.targets:
|
|
199
232
|
summary["criteria"].append(f"Targets: {', '.join(self.targets)}")
|
|
200
233
|
|
|
234
|
+
if self.telescopes:
|
|
235
|
+
summary["criteria"].append(f"Telescopes: {', '.join(self.telescopes)}")
|
|
236
|
+
|
|
201
237
|
if self.date_start or self.date_end:
|
|
202
238
|
date_range = []
|
|
203
239
|
if self.date_start:
|
|
@@ -17,5 +17,37 @@ kind = "preferences"
|
|
|
17
17
|
# website = "https://yourwebsite"
|
|
18
18
|
# license = "FIXME eventually applied as the default license for generated images Creative commons etc..."
|
|
19
19
|
|
|
20
|
+
|
|
21
|
+
# [config]
|
|
22
|
+
# Filters fits fields should we store in our DB cache. By default we store all fits headers.
|
|
23
|
+
#fits-whitelist = [
|
|
24
|
+
# "INSTRUME",
|
|
25
|
+
# "FILTER",
|
|
26
|
+
# "TELESCOP",
|
|
27
|
+
# "IMAGETYP",
|
|
28
|
+
# "DATE-OBS",
|
|
29
|
+
# "DATE-LOC",
|
|
30
|
+
# "DATE",
|
|
31
|
+
# "EXPTIME", # Use use this instead of EXPOSURE because it seems like not all apps use EXPOSURE (Siril)
|
|
32
|
+
# "FWHEEL",
|
|
33
|
+
# "OBJECT",
|
|
34
|
+
# "RA", # we ignore the text version OBJCTRA / OBJCTDEC
|
|
35
|
+
# "DEC",
|
|
36
|
+
# "OBJCTROT",
|
|
37
|
+
# "FOCPOS",
|
|
38
|
+
# "SITELAT",
|
|
39
|
+
# "SITELON",
|
|
40
|
+
# "SITEELEV",
|
|
41
|
+
# "NAXIS1",
|
|
42
|
+
# "NAXIS2",
|
|
43
|
+
# "SWCREATE",
|
|
44
|
+
# "XBINNING",
|
|
45
|
+
# "YBINNING",
|
|
46
|
+
# "GAIN",
|
|
47
|
+
# "CCD-TEMP",
|
|
48
|
+
# "SET-TEMP",
|
|
49
|
+
# "AMBTEMP",
|
|
50
|
+
#]
|
|
51
|
+
|
|
20
52
|
# DO NOT edit below this line, they are managed automatically via
|
|
21
53
|
# the "sb repo add" etc... commands.
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: starbash
|
|
3
|
+
Version: 0.1.3
|
|
4
|
+
Summary:
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Author: Kevin Hester
|
|
7
|
+
Author-email: kevinh@geeksville.com
|
|
8
|
+
Requires-Python: >=3.12,<3.15
|
|
9
|
+
Classifier: Programming Language :: Python :: 3
|
|
10
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
+
Requires-Dist: astropy (>=7.1.1,<8.0.0)
|
|
14
|
+
Requires-Dist: multidict (>=6.7.0,<7.0.0)
|
|
15
|
+
Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
|
|
16
|
+
Requires-Dist: restrictedpython (>=8.1,<9.0)
|
|
17
|
+
Requires-Dist: rich (>=14.2.0,<15.0.0)
|
|
18
|
+
Requires-Dist: sentry-sdk (>=2.42.1,<3.0.0)
|
|
19
|
+
Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
|
|
20
|
+
Requires-Dist: typer (>=0.20.0,<0.21.0)
|
|
21
|
+
Description-Content-Type: text/markdown
|
|
22
|
+
|
|
23
|
+
# Starbash
|
|
24
|
+
|
|
25
|
+

|
|
26
|
+

|
|
27
|
+
|
|
28
|
+

|
|
29
|
+
|
|
30
|
+
A tool for automating/standardizing/sharing astrophotography workflows.
|
|
31
|
+
|
|
32
|
+
# Current status
|
|
33
|
+
|
|
34
|
+
Not quite ready 😊. But making good progress.
|
|
35
|
+
|
|
36
|
+
See the current [TODO](TODO.md) file for work items. I'll be looking for pre-alpha testers/feedback soon.
|
|
37
|
+
|
|
38
|
+
## Current features
|
|
39
|
+
|
|
40
|
+
* Automatically recognizes and auto-parses the default NINA, Asiair and Seestar raw file repos (adding support for other layouts is easy)
|
|
41
|
+
* Multisession support by default (including automatic selection of correct flats, biases and dark frames)
|
|
42
|
+
* 'Repos' can contain raw files, generated masters, preprocessed files, or recipes.
|
|
43
|
+
|
|
44
|
+
## Features coming soon
|
|
45
|
+
|
|
46
|
+
* Automatically performs **complete** preprocessing on OSC (broadband, narrowband or dual Duo filter), Mono (LRGB, SHO) data. i.e. give you 'seestar level' auto-preprocessing, so you only need to do the (optional) custom post-processing.
|
|
47
|
+
* Generates a per target report/config file which can be customized if the detected defaults or preprocessing are not what you want
|
|
48
|
+
* 'Recipes' provide repeatable/human-readable/sharable descriptions of all processing steps
|
|
49
|
+
* Repos can be on the local disk or shared via HTTPS/github/etc. This is particularly useful for recipe repos
|
|
50
|
+
* Uses Siril and Graxpert for its pre-processing operations (support for Pixinsight based recipies will probably be coming at some point...)
|
|
51
|
+
* The target report can be used to auto generate a human friendly 'postable/sharable' report about that image
|
|
52
|
+
* Target reports are sharable so that you can request comments by others and others can rerender with different settings
|
|
53
|
+
|
|
54
|
+
## Installing
|
|
55
|
+
|
|
56
|
+
Currently the easiest way to install this command-line based tool is to install is via [pipx](https://pipx.pypa.io/stable/). If you don't already have pipx and you have python installed, you can auto install it by running "pip install --user pipx." If you don't have python installed see the pipx link for pipx installers for any OS.
|
|
57
|
+
|
|
58
|
+
Once pipx is installed just run:
|
|
59
|
+
|
|
60
|
+
```
|
|
61
|
+
pipx install starbash
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
## Supported commands
|
|
65
|
+
|
|
66
|
+
### Setup & Configuration
|
|
67
|
+
- `sb setup` - Configure starbash via a brief guided process
|
|
68
|
+
- `sb info` - Show user preferences location and other app info
|
|
69
|
+
|
|
70
|
+
### Repository Management
|
|
71
|
+
- `sb repo [--verbose]` - List installed repos (use `-v` for details)
|
|
72
|
+
- `sb repo add <filepath|URL>` - Add a repository
|
|
73
|
+
- `sb repo remove <REPONUM>` - Remove the indicated repo from the repo list
|
|
74
|
+
- `sb repo reindex [--force] [REPONUM]` - Reindex the specified repo (or all repos if none specified)
|
|
75
|
+
|
|
76
|
+
### User Preferences
|
|
77
|
+
- `sb user name "Your Name"` - Set name for attribution in generated images
|
|
78
|
+
- `sb user email "foo@example.com"` - Set email for attribution in generated images
|
|
79
|
+
- `sb user analytics <on|off>` - Turn analytics collection on/off
|
|
80
|
+
|
|
81
|
+
### Selection & Filtering
|
|
82
|
+
- `sb selection` - Show information about the current selection
|
|
83
|
+
- `sb selection any` - Remove all filters (select everything)
|
|
84
|
+
- `sb selection target <TARGETNAME>` - Limit selection to the named target
|
|
85
|
+
- `sb selection telescope <TELESCOPENAME>` - Limit selection to the named telescope
|
|
86
|
+
- `sb selection date <after|before|between> <DATE> [DATE]` - Limit to sessions in the specified date range
|
|
87
|
+
|
|
88
|
+
### Viewing Data
|
|
89
|
+
- `sb session` - List sessions (filtered based on the current selection)
|
|
90
|
+
- `sb target` - List targets (filtered based on the current selection)
|
|
91
|
+
- `sb instrument` - List instruments (filtered based on the current selection)
|
|
92
|
+
- `sb filter` - List all filters found in current selection
|
|
93
|
+
|
|
94
|
+
### Export & Processing
|
|
95
|
+
- `sb export <dirs|BIAS|LIGHT|DARK|FLAT> [DIRLOC]` - Export data
|
|
96
|
+
- `sb process siril` - Generate Siril directory tree and run Siril GUI
|
|
97
|
+
- `sb process auto` - Automatic processing
|
|
98
|
+
- `sb process masters` - Generate master flats, darks, and biases from available raw frames
|
|
99
|
+
|
|
100
|
+
## Supported tools (now)
|
|
101
|
+
|
|
102
|
+
* Siril
|
|
103
|
+
* Graxpert
|
|
104
|
+
* Python (you can add python code to recipies if necessary)
|
|
105
|
+
|
|
106
|
+
## Supported tools (future?)
|
|
107
|
+
|
|
108
|
+
* Pixinsight?
|
|
109
|
+
* Autostakkert?
|
|
110
|
+
|
|
111
|
+
## Developing
|
|
112
|
+
|
|
113
|
+
We try to make this project useful and friendly. If you find problems please file a github issue.
|
|
114
|
+
We accept pull-requests and enjoy discussing possible new development directions via github issues. If you might want to work on this, just describe what your interests are and we can talk about how to get it merged.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
starbash/__init__.py,sha256=co39eIssQlFxWfO3cDhp52reRy6qEyJX5u5K8OsxiDk,138
|
|
2
|
+
starbash/analytics.py,sha256=0TfiZthKRMqW38Jg1VJDjpykZXBrK33tNKuhQibkCK0,3579
|
|
3
|
+
starbash/app.py,sha256=7GdznZHSxpsQ5ZrW0nPgvSEGAwgijfpbw2rINNU1hcU,16228
|
|
4
|
+
starbash/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
+
starbash/commands/repo.py,sha256=UyfAD6A0fP8DV1uMRJz_NydDkP0UiHbxtceZ25_g7r8,4641
|
|
6
|
+
starbash/commands/selection.py,sha256=gI6LSvs8CW5t2pW0peCLL5O87WqLkS9LDuaK2gI1NeM,4600
|
|
7
|
+
starbash/commands/user.py,sha256=JpwYa9cYxm2gkekcxrj7CsbJO7fgbGwRxmjvpua0BOY,1598
|
|
8
|
+
starbash/database.py,sha256=59u78srp3G4Ya0HOIP2qOokp2osQPFgUQM_8TbfIjPg,17936
|
|
9
|
+
starbash/defaults/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
+
starbash/defaults/starbash.toml,sha256=WWM7igZ-GXk4bM7qSFtjHaVh7beqisCkeVBjs7c2G6I,1774
|
|
11
|
+
starbash/main.py,sha256=8D4Xa0WwM3TDW-HkaQUTo79Uh8hAXgOJYyEEkUgC7HQ,5391
|
|
12
|
+
starbash/paths.py,sha256=BKKnSXt3tOh16o7ljDcQLtWKIiepEmud9JFtzRwDHtg,1317
|
|
13
|
+
starbash/repo/__init__.py,sha256=TqspuLjPSNnO38tvCGa0fJvvasgecHl6fE7m0-Lj8ho,148
|
|
14
|
+
starbash/repo/manager.py,sha256=NRMGmDWt8KV8OdsrgPiuQ91EPi8xlSbVtOWGgSePuOk,10944
|
|
15
|
+
starbash/selection.py,sha256=DPzUlls3n-sBqkwTUns2ZNaPi61PGnh7Z_ZQOC1jXYc,8347
|
|
16
|
+
starbash/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
+
starbash/templates/userconfig.toml,sha256=eaBMgac4RCdvlGV6cKPqRuVyF_ijfkvEF-c3LJ6_euw,1440
|
|
18
|
+
starbash/tool.py,sha256=S1kOTbeHTrA0meqwftgL0SA4VhJdZWWx2h1Wtwu1Izg,8749
|
|
19
|
+
starbash/url.py,sha256=lorxQJ27jSfzsKCb0QvpcvLiPZG55Dkd_c1JPFbni4I,402
|
|
20
|
+
starbash-0.1.3.dist-info/METADATA,sha256=zdFUMvEJQ8Aq28LTkcQDtvY_uu6G7Efy5w2FTH4IH_E,5295
|
|
21
|
+
starbash-0.1.3.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
22
|
+
starbash-0.1.3.dist-info/entry_points.txt,sha256=REQyWs8e5TJsNK7JVVWowKVoytMmKlUwuFHLTmSX4hc,67
|
|
23
|
+
starbash-0.1.3.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
24
|
+
starbash-0.1.3.dist-info/RECORD,,
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
Metadata-Version: 2.4
|
|
2
|
-
Name: starbash
|
|
3
|
-
Version: 0.1.1
|
|
4
|
-
Summary:
|
|
5
|
-
License-File: LICENSE
|
|
6
|
-
Author: Kevin Hester
|
|
7
|
-
Author-email: kevinh@geeksville.com
|
|
8
|
-
Requires-Python: >=3.12,<3.15
|
|
9
|
-
Classifier: Programming Language :: Python :: 3
|
|
10
|
-
Classifier: Programming Language :: Python :: 3.12
|
|
11
|
-
Classifier: Programming Language :: Python :: 3.13
|
|
12
|
-
Classifier: Programming Language :: Python :: 3.14
|
|
13
|
-
Requires-Dist: astropy (>=7.1.1,<8.0.0)
|
|
14
|
-
Requires-Dist: multidict (>=6.7.0,<7.0.0)
|
|
15
|
-
Requires-Dist: platformdirs (>=4.5.0,<5.0.0)
|
|
16
|
-
Requires-Dist: restrictedpython (>=8.1,<9.0)
|
|
17
|
-
Requires-Dist: rich (>=14.2.0,<15.0.0)
|
|
18
|
-
Requires-Dist: sentry-sdk (>=2.42.1,<3.0.0)
|
|
19
|
-
Requires-Dist: tomlkit (>=0.13.3,<0.14.0)
|
|
20
|
-
Requires-Dist: typer (>=0.20.0,<0.21.0)
|
|
21
|
-
Description-Content-Type: text/markdown
|
|
22
|
-
|
|
23
|
-
# Starbash
|
|
24
|
-
|
|
25
|
-

|
|
26
|
-

|
|
27
|
-
|
|
28
|
-

|
|
29
|
-
|
|
30
|
-
A tool for automating/standardizing/sharing astrophotography workflows.
|
|
31
|
-
|
|
32
|
-
# Current status
|
|
33
|
-
|
|
34
|
-
Not quite ready 😊. But making good progress.
|
|
35
|
-
|
|
36
|
-
See my personal [TODO](TODO.md) file. I'll be looking for pre-alpha testers/feedback soon.
|
|
37
|
-
|
|
38
|
-
## features
|
|
39
|
-
|
|
40
|
-
* Automatically recognizes and auto-parses the default NINA, Asiair and Seestar raw file repo layouts (adding support for other layouts is easy)
|
|
41
|
-
* Automatically performs preprocessing on OSC (broadband, narrowband or dual Duo filter), Mono (LRGB, SHO) data
|
|
42
|
-
* Multisession support by default (including auto selection of correct flats, biases and dark frames)
|
|
43
|
-
* Generates a per target report/config file which can be customized if the detected defaults are not what you want
|
|
44
|
-
* 'Recipes' provide repeatable/human-readable/sharable descriptions of all processing steps
|
|
45
|
-
* 'Repos' can contain raw files, generated masters, preprocessed files, or recipes.
|
|
46
|
-
* Repos can be on the local disk or shared via HTTPS/github/etc. This is particularly useful for recipe repos
|
|
47
|
-
|
|
48
|
-
## Supported commands
|
|
49
|
-
|
|
50
|
-
* setup - configure for you via a brief guided process
|
|
51
|
-
* info - show user preferences location and other app info
|
|
52
|
-
|
|
53
|
-
* repo add file/path|URL
|
|
54
|
-
* repo remove REPONAME|REPONUM
|
|
55
|
-
* repo list
|
|
56
|
-
* repo reindex REPONAME|REPONUM|all
|
|
57
|
-
|
|
58
|
-
* user analytics on|off - turn analytics collection on/off
|
|
59
|
-
* user name "Your Name" - used for attribution in generated images
|
|
60
|
-
* user email "foo@blah.com" - used for attribution in generated images
|
|
61
|
-
|
|
62
|
-
* selection any - remove any filters on sessions, etc...
|
|
63
|
-
* selection target TARGETNAME - limit the current selection to only the named targets
|
|
64
|
-
* selection date op DATE - limit to sessions in the specified date range
|
|
65
|
-
* selection - list information about the current selection
|
|
66
|
-
|
|
67
|
-
* target - list targets (filtered based on the current selection)
|
|
68
|
-
|
|
69
|
-
* session- list sessions (filtered based on the current selection)
|
|
70
|
-
|
|
71
|
-
* instrument - list instruments (filtered based on the current selection)
|
|
72
|
-
|
|
73
|
-
* filter - list all filters found in current selection
|
|
74
|
-
|
|
75
|
-
* export dirs|BIAS|LIGHT|DARK|FLAT [DIRLOC]
|
|
76
|
-
|
|
77
|
-
* process auto
|
|
78
|
-
* process masters - generate master flats, darks, biases from any raws that are available
|
|
79
|
-
|
|
80
|
-
## Supported tools
|
|
81
|
-
|
|
82
|
-
* Siril
|
|
83
|
-
* Graxpert
|
|
84
|
-
|
|
85
|
-
# Future status
|
|
86
|
-
|
|
87
|
-
## Supported tools
|
|
88
|
-
|
|
89
|
-
* Pixinsight?
|
|
90
|
-
* Autostakkert?
|
|
91
|
-
|
|
92
|
-
## Features
|
|
93
|
-
|
|
94
|
-
* The target report can be used to auto generate a human friendly 'postable/sharable' report about that image
|
|
95
|
-
* Target reports are sharable so that you can request comments by others and others can rerender with different settings
|
|
96
|
-
|
starbash-0.1.1.dist-info/RECORD
DELETED
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
starbash/__init__.py,sha256=co39eIssQlFxWfO3cDhp52reRy6qEyJX5u5K8OsxiDk,138
|
|
2
|
-
starbash/analytics.py,sha256=0TfiZthKRMqW38Jg1VJDjpykZXBrK33tNKuhQibkCK0,3579
|
|
3
|
-
starbash/app.py,sha256=sYEgemSDZEX7yih2p_aFlrsCypWjx2zNDsNHJ_DNF0E,13319
|
|
4
|
-
starbash/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
5
|
-
starbash/commands/repo.py,sha256=WuOg15p9LnJ6aIWIXYCKY9guRIUCq3XapyO7oi1v3hQ,1874
|
|
6
|
-
starbash/commands/selection.py,sha256=4bpjYWne9ekg1EIOJS15Ymj2Hyr-jc2Te4N4mXepkpI,3941
|
|
7
|
-
starbash/commands/user.py,sha256=JpwYa9cYxm2gkekcxrj7CsbJO7fgbGwRxmjvpua0BOY,1598
|
|
8
|
-
starbash/database.py,sha256=jQyuZ-sFwbAIv6Wtn4VnPVQChQMP4y1rVjrEgp2iLoA,13513
|
|
9
|
-
starbash/defaults/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
|
-
starbash/defaults/starbash.toml,sha256=RVfYikW5xhpZUt83U5k7O2hprswgFvDFykCWkHL37QU,2384
|
|
11
|
-
starbash/main.py,sha256=7N28XCu43IUijIaTrT9ZY_aWxEmJGDdTLWdfhw_omOc,5084
|
|
12
|
-
starbash/paths.py,sha256=BKKnSXt3tOh16o7ljDcQLtWKIiepEmud9JFtzRwDHtg,1317
|
|
13
|
-
starbash/repo/__init__.py,sha256=TqspuLjPSNnO38tvCGa0fJvvasgecHl6fE7m0-Lj8ho,148
|
|
14
|
-
starbash/repo/manager.py,sha256=XBiZXMgVKd7faddwugtWVbeR8XuJF8sZi8yAiDxT6wM,10701
|
|
15
|
-
starbash/selection.py,sha256=4u4h30VeC_e8PWFdtNWA9C7AyYrcAgg3BEagSynaxMM,7111
|
|
16
|
-
starbash/templates/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
17
|
-
starbash/templates/userconfig.toml,sha256=NinuXl8L0xxSvI4FFZD8ye836yv8TaUP0a5VMEc-w1w,757
|
|
18
|
-
starbash/tool.py,sha256=S1kOTbeHTrA0meqwftgL0SA4VhJdZWWx2h1Wtwu1Izg,8749
|
|
19
|
-
starbash/url.py,sha256=lorxQJ27jSfzsKCb0QvpcvLiPZG55Dkd_c1JPFbni4I,402
|
|
20
|
-
starbash-0.1.1.dist-info/METADATA,sha256=EjekS6hjvRkPf-Ka0ZUa0_XhRp50YwABIjOBlStvyH4,3539
|
|
21
|
-
starbash-0.1.1.dist-info/WHEEL,sha256=zp0Cn7JsFoX2ATtOhtaFYIiE2rmFAD4OcMhtUki8W3U,88
|
|
22
|
-
starbash-0.1.1.dist-info/entry_points.txt,sha256=REQyWs8e5TJsNK7JVVWowKVoytMmKlUwuFHLTmSX4hc,67
|
|
23
|
-
starbash-0.1.1.dist-info/licenses/LICENSE,sha256=ixuiBLtpoK3iv89l7ylKkg9rs2GzF9ukPH7ynZYzK5s,35148
|
|
24
|
-
starbash-0.1.1.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|