starbash 0.1.6__py3-none-any.whl → 0.1.9__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.
- {starbash/repo → repo}/__init__.py +2 -1
- repo/manager.py +144 -0
- starbash/repo/manager.py → repo/repo.py +29 -116
- starbash/__init__.py +28 -1
- starbash/analytics.py +6 -7
- starbash/app.py +449 -86
- starbash/commands/__init__.py +7 -0
- starbash/commands/info.py +53 -25
- starbash/commands/process.py +154 -0
- starbash/commands/repo.py +168 -77
- starbash/commands/select.py +157 -68
- starbash/database.py +252 -135
- starbash/defaults/starbash.toml +17 -0
- starbash/main.py +4 -1
- starbash/recipes/master_bias/starbash.toml +25 -8
- starbash/recipes/starbash.toml +5 -0
- starbash/selection.py +109 -45
- starbash/templates/repo/master.toml +13 -0
- starbash/templates/userconfig.toml +1 -1
- starbash/toml.py +29 -0
- starbash/tool.py +84 -12
- {starbash-0.1.6.dist-info → starbash-0.1.9.dist-info}/METADATA +37 -16
- starbash-0.1.9.dist-info/RECORD +37 -0
- starbash-0.1.6.dist-info/RECORD +0 -33
- {starbash-0.1.6.dist-info → starbash-0.1.9.dist-info}/WHEEL +0 -0
- {starbash-0.1.6.dist-info → starbash-0.1.9.dist-info}/entry_points.txt +0 -0
- {starbash-0.1.6.dist-info → starbash-0.1.9.dist-info}/licenses/LICENSE +0 -0
starbash/commands/select.py
CHANGED
|
@@ -1,20 +1,80 @@
|
|
|
1
1
|
"""Selection commands for filtering sessions and targets."""
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
+
from typing import Any
|
|
5
|
+
from collections import Counter
|
|
4
6
|
import typer
|
|
5
7
|
from pathlib import Path
|
|
6
8
|
from typing_extensions import Annotated
|
|
7
|
-
from datetime import datetime
|
|
8
9
|
from rich.table import Table
|
|
10
|
+
import logging
|
|
9
11
|
|
|
12
|
+
import starbash
|
|
13
|
+
from starbash import to_shortdate
|
|
10
14
|
from starbash.app import Starbash, copy_images_to_dir
|
|
11
|
-
from starbash.database import Database
|
|
15
|
+
from starbash.database import Database, SessionRow, get_column_name
|
|
12
16
|
from starbash import console
|
|
13
|
-
from starbash.commands import
|
|
17
|
+
from starbash.commands import (
|
|
18
|
+
format_duration,
|
|
19
|
+
TABLE_COLUMN_STYLE,
|
|
20
|
+
TABLE_VALUE_STYLE,
|
|
21
|
+
)
|
|
14
22
|
|
|
15
23
|
app = typer.Typer()
|
|
16
24
|
|
|
17
25
|
|
|
26
|
+
def get_column(sb: Starbash, column_name: str) -> Counter:
|
|
27
|
+
|
|
28
|
+
# Also do a complete unfiltered search so we can compare for the users
|
|
29
|
+
allsessions = sb.db.search_session(("", []))
|
|
30
|
+
|
|
31
|
+
column_name = get_column_name(column_name)
|
|
32
|
+
allfound = [session[column_name] for session in allsessions if session[column_name]]
|
|
33
|
+
|
|
34
|
+
# Count occurrences of each telescope
|
|
35
|
+
all_counts = Counter(allfound)
|
|
36
|
+
|
|
37
|
+
return all_counts
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def complete_date(incomplete: str, column_name: str):
|
|
41
|
+
"""calls get_column() and assumes the returned str->count object has iso datetime strings as the keys
|
|
42
|
+
it merges the counts for all dates that are on the same local timezone day.
|
|
43
|
+
in the returned str->count, just include the date portion (YYYY-MM-DD)."""
|
|
44
|
+
|
|
45
|
+
# We need to use stderr_logging to prevent confusing the bash completion parser
|
|
46
|
+
starbash.log_filter_level = (
|
|
47
|
+
logging.ERROR
|
|
48
|
+
) # avoid showing output while doing completion
|
|
49
|
+
with Starbash("select.complete.date", stderr_logging=True) as sb:
|
|
50
|
+
c = get_column(sb, column_name)
|
|
51
|
+
|
|
52
|
+
# Merge counts by date (YYYY-MM-DD) in local timezone
|
|
53
|
+
date_counts = Counter()
|
|
54
|
+
for datetime_str, count in c.items():
|
|
55
|
+
# Extract just the date portion (YYYY-MM-DD) from local datetime
|
|
56
|
+
date_only = to_shortdate(datetime_str)
|
|
57
|
+
date_counts[date_only] += count
|
|
58
|
+
|
|
59
|
+
# Yield completions matching the incomplete input
|
|
60
|
+
for date, count in sorted(date_counts.items(), reverse=True):
|
|
61
|
+
if date.startswith(incomplete):
|
|
62
|
+
yield (date, f"{count} sessions")
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def complete_column(incomplete: str, column_name: str):
|
|
66
|
+
# We need to use stderr_logging to prevent confusing the bash completion parser
|
|
67
|
+
starbash.log_filter_level = (
|
|
68
|
+
logging.ERROR
|
|
69
|
+
) # avoid showing output while doing completion
|
|
70
|
+
with Starbash("repo.complete.column", stderr_logging=True) as sb:
|
|
71
|
+
c = get_column(sb, column_name)
|
|
72
|
+
|
|
73
|
+
for item, count in c.items():
|
|
74
|
+
if item.startswith(incomplete):
|
|
75
|
+
yield (item, f"{count} sessions")
|
|
76
|
+
|
|
77
|
+
|
|
18
78
|
@app.command(name="any")
|
|
19
79
|
def clear():
|
|
20
80
|
"""Remove any filters on sessions, etc... (select everything)."""
|
|
@@ -28,7 +88,10 @@ def target(
|
|
|
28
88
|
target_name: Annotated[
|
|
29
89
|
str,
|
|
30
90
|
typer.Argument(
|
|
31
|
-
help="Target name to add to the selection (e.g., 'M31', 'NGC 7000')"
|
|
91
|
+
help="Target name to add to the selection (e.g., 'M31', 'NGC 7000')",
|
|
92
|
+
autocompletion=lambda incomplete: complete_column(
|
|
93
|
+
incomplete, Database.OBJECT_KEY
|
|
94
|
+
),
|
|
32
95
|
),
|
|
33
96
|
],
|
|
34
97
|
):
|
|
@@ -46,7 +109,10 @@ def telescope(
|
|
|
46
109
|
telescope_name: Annotated[
|
|
47
110
|
str,
|
|
48
111
|
typer.Argument(
|
|
49
|
-
help="Telescope name to add to the selection (e.g., 'Vespera', 'EdgeHD 8')"
|
|
112
|
+
help="Telescope name to add to the selection (e.g., 'Vespera', 'EdgeHD 8')",
|
|
113
|
+
autocompletion=lambda incomplete: complete_column(
|
|
114
|
+
incomplete, Database.TELESCOP_KEY
|
|
115
|
+
),
|
|
50
116
|
),
|
|
51
117
|
],
|
|
52
118
|
):
|
|
@@ -61,21 +127,41 @@ def telescope(
|
|
|
61
127
|
)
|
|
62
128
|
|
|
63
129
|
|
|
130
|
+
def complete_name(incomplete: str, names: list[str]):
|
|
131
|
+
"""Return typer style autocompletion from a list of string constants."""
|
|
132
|
+
for name in names:
|
|
133
|
+
if name.startswith(incomplete):
|
|
134
|
+
yield name
|
|
135
|
+
|
|
136
|
+
|
|
64
137
|
@app.command()
|
|
65
138
|
def date(
|
|
66
139
|
operation: Annotated[
|
|
67
140
|
str,
|
|
68
|
-
typer.Argument(
|
|
141
|
+
typer.Argument(
|
|
142
|
+
help="Date operation: 'after', 'before', or 'between'",
|
|
143
|
+
autocompletion=lambda incomplete: complete_name(
|
|
144
|
+
incomplete, ["after", "before", "between"]
|
|
145
|
+
),
|
|
146
|
+
),
|
|
69
147
|
],
|
|
70
148
|
date_value: Annotated[
|
|
71
149
|
str,
|
|
72
150
|
typer.Argument(
|
|
73
|
-
help="Date in ISO format (YYYY-MM-DD) or two dates separated by space for 'between'"
|
|
151
|
+
help="Date in ISO format (YYYY-MM-DD) or two dates separated by space for 'between'",
|
|
152
|
+
autocompletion=lambda incomplete: complete_date(
|
|
153
|
+
incomplete, Database.START_KEY
|
|
154
|
+
),
|
|
74
155
|
),
|
|
75
156
|
],
|
|
76
157
|
end_date: Annotated[
|
|
77
158
|
str | None,
|
|
78
|
-
typer.Argument(
|
|
159
|
+
typer.Argument(
|
|
160
|
+
help="End date for 'between' operation (YYYY-MM-DD)",
|
|
161
|
+
autocompletion=lambda incomplete: complete_date(
|
|
162
|
+
incomplete, Database.START_KEY
|
|
163
|
+
),
|
|
164
|
+
),
|
|
79
165
|
] = None,
|
|
80
166
|
):
|
|
81
167
|
"""Limit to sessions in the specified date range.
|
|
@@ -116,7 +202,14 @@ def date(
|
|
|
116
202
|
|
|
117
203
|
|
|
118
204
|
@app.command(name="list")
|
|
119
|
-
def list_sessions(
|
|
205
|
+
def list_sessions(
|
|
206
|
+
verbose: bool = typer.Option(
|
|
207
|
+
False,
|
|
208
|
+
"--verbose",
|
|
209
|
+
"-v",
|
|
210
|
+
help="Show all sessions (normally Dark/Bias/Flat are hidden)",
|
|
211
|
+
)
|
|
212
|
+
):
|
|
120
213
|
"""List sessions (filtered based on the current selection)"""
|
|
121
214
|
|
|
122
215
|
with Starbash("selection.list") as sb:
|
|
@@ -127,14 +220,14 @@ def list_sessions():
|
|
|
127
220
|
sb.analytics.set_data("session.num_selected", len(sessions))
|
|
128
221
|
sb.analytics.set_data("session.num_total", len_all)
|
|
129
222
|
|
|
130
|
-
table.add_column("#", style=
|
|
131
|
-
table.add_column("Date", style=
|
|
132
|
-
table.add_column("# images", style=
|
|
133
|
-
table.add_column("Time", style=
|
|
134
|
-
table.add_column("Type/Filter", style=
|
|
135
|
-
table.add_column("Telescope", style=
|
|
223
|
+
table.add_column("#", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
224
|
+
table.add_column("Date", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
225
|
+
table.add_column("# images", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
226
|
+
table.add_column("Time", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
227
|
+
table.add_column("Type/Filter", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
228
|
+
table.add_column("Telescope", style=TABLE_COLUMN_STYLE, no_wrap=True)
|
|
136
229
|
table.add_column(
|
|
137
|
-
"About", style=
|
|
230
|
+
"About", style=TABLE_COLUMN_STYLE, no_wrap=True
|
|
138
231
|
) # type of frames, filter, target
|
|
139
232
|
|
|
140
233
|
total_images = 0
|
|
@@ -143,26 +236,25 @@ def list_sessions():
|
|
|
143
236
|
image_types = set()
|
|
144
237
|
telescopes = set()
|
|
145
238
|
|
|
239
|
+
def get_key(k: str, default: Any = "N/A") -> Any:
|
|
240
|
+
"""Convert keynames to SQL legal column names"""
|
|
241
|
+
k = get_column_name(k)
|
|
242
|
+
return sess.get(k, default)
|
|
243
|
+
|
|
146
244
|
for session_index, sess in enumerate(sessions):
|
|
147
|
-
date_iso =
|
|
148
|
-
|
|
149
|
-
try:
|
|
150
|
-
dt_utc = datetime.fromisoformat(date_iso)
|
|
151
|
-
dt_local = dt_utc.astimezone()
|
|
152
|
-
date = dt_local.strftime("%Y-%m-%d")
|
|
153
|
-
except (ValueError, TypeError):
|
|
154
|
-
date = date_iso
|
|
245
|
+
date_iso = get_key(Database.START_KEY)
|
|
246
|
+
date = to_shortdate(date_iso)
|
|
155
247
|
|
|
156
|
-
object =
|
|
157
|
-
filter =
|
|
248
|
+
object = get_key(Database.OBJECT_KEY)
|
|
249
|
+
filter = get_key(Database.FILTER_KEY)
|
|
158
250
|
filters.add(filter)
|
|
159
|
-
image_type =
|
|
251
|
+
image_type = get_key(Database.IMAGETYP_KEY)
|
|
160
252
|
image_types.add(image_type)
|
|
161
|
-
telescope =
|
|
253
|
+
telescope = get_key(Database.TELESCOP_KEY)
|
|
162
254
|
telescopes.add(telescope)
|
|
163
255
|
|
|
164
256
|
# Format total exposure time as integer seconds
|
|
165
|
-
exptime_raw =
|
|
257
|
+
exptime_raw = get_key(Database.EXPTIME_TOTAL_KEY)
|
|
166
258
|
try:
|
|
167
259
|
exptime_float = float(exptime_raw)
|
|
168
260
|
total_seconds += exptime_float
|
|
@@ -172,10 +264,10 @@ def list_sessions():
|
|
|
172
264
|
|
|
173
265
|
# Count images
|
|
174
266
|
try:
|
|
175
|
-
num_images = int(
|
|
267
|
+
num_images = int(get_key(Database.NUM_IMAGES_KEY, 0))
|
|
176
268
|
total_images += num_images
|
|
177
269
|
except (ValueError, TypeError):
|
|
178
|
-
num_images =
|
|
270
|
+
num_images = get_key(Database.NUM_IMAGES_KEY)
|
|
179
271
|
|
|
180
272
|
type_str = image_type
|
|
181
273
|
if image_type.upper() == "LIGHT":
|
|
@@ -218,6 +310,32 @@ def list_sessions():
|
|
|
218
310
|
sb.analytics.set_data("session.image_types", image_types)
|
|
219
311
|
|
|
220
312
|
|
|
313
|
+
def selection_by_number(
|
|
314
|
+
sb: Starbash,
|
|
315
|
+
session_num: int,
|
|
316
|
+
) -> SessionRow:
|
|
317
|
+
"""Get the session corresponding to the given session number in the current selection."""
|
|
318
|
+
# Get the filtered sessions
|
|
319
|
+
sessions = sb.search_session()
|
|
320
|
+
|
|
321
|
+
if not sessions or not isinstance(sessions, list):
|
|
322
|
+
console.print("[red]No sessions found. Check your selection criteria.[/red]")
|
|
323
|
+
raise typer.Exit(1)
|
|
324
|
+
|
|
325
|
+
# Validate session number
|
|
326
|
+
if session_num < 1 or session_num > len(sessions):
|
|
327
|
+
console.print(
|
|
328
|
+
f"[red]Error: Session number {session_num} is out of range. "
|
|
329
|
+
f"Valid range is 1-{len(sessions)}.[/red]"
|
|
330
|
+
)
|
|
331
|
+
console.print("[yellow]Use 'select list' to see available sessions.[/yellow]")
|
|
332
|
+
raise typer.Exit(1)
|
|
333
|
+
|
|
334
|
+
# Get the selected session (convert from 1-based to 0-based index)
|
|
335
|
+
session = sessions[session_num - 1]
|
|
336
|
+
return session
|
|
337
|
+
|
|
338
|
+
|
|
221
339
|
@app.command()
|
|
222
340
|
def export(
|
|
223
341
|
session_num: Annotated[
|
|
@@ -237,36 +355,16 @@ def export(
|
|
|
237
355
|
The session number corresponds to the '#' column in 'select list' output.
|
|
238
356
|
"""
|
|
239
357
|
with Starbash("selection.export") as sb:
|
|
240
|
-
# Get the filtered sessions
|
|
241
|
-
sessions = sb.search_session()
|
|
242
|
-
|
|
243
|
-
if not sessions or not isinstance(sessions, list):
|
|
244
|
-
console.print(
|
|
245
|
-
"[red]No sessions found. Check your selection criteria.[/red]"
|
|
246
|
-
)
|
|
247
|
-
raise typer.Exit(1)
|
|
248
|
-
|
|
249
|
-
# Validate session number
|
|
250
|
-
if session_num < 1 or session_num > len(sessions):
|
|
251
|
-
console.print(
|
|
252
|
-
f"[red]Error: Session number {session_num} is out of range. "
|
|
253
|
-
f"Valid range is 1-{len(sessions)}.[/red]"
|
|
254
|
-
)
|
|
255
|
-
console.print(
|
|
256
|
-
"[yellow]Use 'select list' to see available sessions.[/yellow]"
|
|
257
|
-
)
|
|
258
|
-
raise typer.Exit(1)
|
|
259
|
-
|
|
260
358
|
# Get the selected session (convert from 1-based to 0-based index)
|
|
261
|
-
session =
|
|
359
|
+
session = selection_by_number(sb, session_num)
|
|
262
360
|
|
|
263
|
-
# Get
|
|
264
|
-
|
|
265
|
-
if
|
|
361
|
+
# Get images for this session
|
|
362
|
+
images = sb.get_session_images(session)
|
|
363
|
+
if not images:
|
|
266
364
|
console.print(
|
|
267
|
-
f"[red]Error:
|
|
365
|
+
f"[red]Error: No images found for session {session_num}.[/red]"
|
|
268
366
|
)
|
|
269
|
-
raise typer.Exit(
|
|
367
|
+
raise typer.Exit(0)
|
|
270
368
|
|
|
271
369
|
# Determine output directory
|
|
272
370
|
output_dir = Path(destdir)
|
|
@@ -274,15 +372,6 @@ def export(
|
|
|
274
372
|
# Create output directory if it doesn't exist
|
|
275
373
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
276
374
|
|
|
277
|
-
# Get images for this session
|
|
278
|
-
images = sb.get_session_images(session_id)
|
|
279
|
-
|
|
280
|
-
if not images:
|
|
281
|
-
console.print(
|
|
282
|
-
f"[yellow]Warning: No images found for session {session_num}.[/yellow]"
|
|
283
|
-
)
|
|
284
|
-
raise typer.Exit(0)
|
|
285
|
-
|
|
286
375
|
copy_images_to_dir(images, output_dir)
|
|
287
376
|
|
|
288
377
|
|
|
@@ -300,8 +389,8 @@ def show_selection(ctx: typer.Context):
|
|
|
300
389
|
console.print(f"[yellow]{summary['message']}[/yellow]")
|
|
301
390
|
else:
|
|
302
391
|
table = Table(title="Current Selection")
|
|
303
|
-
table.add_column("Criteria", style=
|
|
304
|
-
table.add_column("Value", style=
|
|
392
|
+
table.add_column("Criteria", style=TABLE_COLUMN_STYLE)
|
|
393
|
+
table.add_column("Value", style=TABLE_VALUE_STYLE)
|
|
305
394
|
|
|
306
395
|
for criterion in summary["criteria"]:
|
|
307
396
|
parts = criterion.split(": ", 1)
|