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/database.py CHANGED
@@ -1,23 +1,59 @@
1
1
  from __future__ import annotations
2
2
 
3
- import logging
4
3
  import sqlite3
5
4
  from pathlib import Path
6
5
  from typing import Any, Optional
7
6
  from datetime import datetime, timedelta
8
7
  import json
8
+ from typing import TypeAlias
9
9
 
10
10
  from .paths import get_user_data_dir
11
11
 
12
+ SessionRow: TypeAlias = dict[str, Any]
13
+ ImageRow: TypeAlias = dict[str, Any]
14
+
15
+
16
+ def get_column_name(k: str) -> str:
17
+ """Convert keynames to SQL legal column names"""
18
+ k = k.lower()
19
+ k = k.replace(" ", "_")
20
+ k = k.replace("-", "_")
21
+ return k
22
+
12
23
 
13
24
  class Database:
14
25
  """SQLite-backed application database.
15
26
 
16
27
  Stores data under the OS-specific user data directory using platformdirs.
28
+
29
+ Tables:
30
+ #1: repos
31
+ A table with one row per repository. Contains only 'id' (primary key) and 'url' (unique).
32
+ The URL identifies the repository root (e.g., 'file:///path/to/repo').
33
+
34
+ #2: images
17
35
  Provides an `images` table for FITS metadata and basic helpers.
18
36
 
19
37
  The images table stores DATE-OBS and DATE as indexed SQL columns for
20
38
  efficient date-based queries, while other FITS metadata is stored in JSON.
39
+
40
+ The 'path' column contains a path **relative** to the repository root.
41
+ Each image belongs to exactly one repo, linked via the repo_id foreign key.
42
+ The combination of (repo_id, path) is unique.
43
+
44
+ Image retrieval methods (get_image, search_image, all_images) join with the repos
45
+ table to include repo_url in results, allowing callers to reconstruct absolute paths.
46
+
47
+ #3: sessions
48
+ The sessions table has one row per observing session, summarizing key info.
49
+ Sessions are identified by filter, image type, target, telescope, etc, and start/end times.
50
+ They correspond to groups of images taken together during an observing run (e.g.
51
+ session start/end describes the range of images DATE-OBS).
52
+
53
+ Each session also has an image_doc_id field which will point to a representative
54
+ image in the images table. Eventually we'll use joins to add extra info from images to
55
+ the exposed 'session' row.
56
+
21
57
  """
22
58
 
23
59
  EXPTIME_KEY = "EXPTIME"
@@ -28,13 +64,15 @@ class Database:
28
64
  EXPTIME_TOTAL_KEY = "exptime-total"
29
65
  DATE_OBS_KEY = "DATE-OBS"
30
66
  DATE_KEY = "DATE"
31
- IMAGE_DOC_KEY = "image-doc"
67
+ IMAGE_DOC_KEY = "image-doc-id"
32
68
  IMAGETYP_KEY = "IMAGETYP"
33
69
  OBJECT_KEY = "OBJECT"
34
70
  TELESCOP_KEY = "TELESCOP"
71
+ ID_KEY = "id" # for finding any row by its ID
35
72
 
36
73
  SESSIONS_TABLE = "sessions"
37
74
  IMAGES_TABLE = "images"
75
+ REPOS_TABLE = "repos"
38
76
 
39
77
  def __init__(
40
78
  self,
@@ -57,18 +95,38 @@ class Database:
57
95
  self._init_tables()
58
96
 
59
97
  def _init_tables(self) -> None:
60
- """Create the images and sessions tables if they don't exist."""
98
+ """Create the repos, images and sessions tables if they don't exist."""
61
99
  cursor = self._db.cursor()
62
100
 
101
+ # Create repos table
102
+ cursor.execute(
103
+ f"""
104
+ CREATE TABLE IF NOT EXISTS {self.REPOS_TABLE} (
105
+ id INTEGER PRIMARY KEY AUTOINCREMENT,
106
+ url TEXT UNIQUE NOT NULL
107
+ )
108
+ """
109
+ )
110
+
111
+ # Create index on url for faster lookups
112
+ cursor.execute(
113
+ f"""
114
+ CREATE INDEX IF NOT EXISTS idx_repos_url ON {self.REPOS_TABLE}(url)
115
+ """
116
+ )
117
+
63
118
  # Create images table with DATE-OBS and DATE as indexed columns
64
119
  cursor.execute(
65
120
  f"""
66
121
  CREATE TABLE IF NOT EXISTS {self.IMAGES_TABLE} (
67
122
  id INTEGER PRIMARY KEY AUTOINCREMENT,
68
- path TEXT UNIQUE NOT NULL,
123
+ repo_id INTEGER NOT NULL,
124
+ path TEXT NOT NULL,
69
125
  date_obs TEXT,
70
126
  date TEXT,
71
- metadata TEXT NOT NULL
127
+ metadata TEXT NOT NULL,
128
+ FOREIGN KEY (repo_id) REFERENCES {self.REPOS_TABLE}(id),
129
+ UNIQUE(repo_id, path)
72
130
  )
73
131
  """
74
132
  )
@@ -107,7 +165,8 @@ class Database:
107
165
  telescop TEXT NOT NULL,
108
166
  num_images INTEGER NOT NULL,
109
167
  exptime_total REAL NOT NULL,
110
- image_doc_id INTEGER
168
+ image_doc_id INTEGER,
169
+ FOREIGN KEY (image_doc_id) REFERENCES {self.IMAGES_TABLE}(id)
111
170
  )
112
171
  """
113
172
  )
@@ -122,18 +181,125 @@ class Database:
122
181
 
123
182
  self._db.commit()
124
183
 
184
+ # --- Convenience helpers for common repo operations ---
185
+ def remove_repo(self, url: str) -> None:
186
+ """Remove a repo record by URL.
187
+
188
+ This will cascade delete all images belonging to this repo, and all sessions
189
+ that reference those images.
190
+
191
+ Args:
192
+ url: The repository URL (e.g., 'file:///path/to/repo')
193
+ """
194
+ cursor = self._db.cursor()
195
+
196
+ # First get the repo_id
197
+ repo_id = self.get_repo_id(url)
198
+ if repo_id is None:
199
+ return # Repo doesn't exist, nothing to delete
200
+
201
+ # Delete sessions that reference images from this repo
202
+ # This deletes sessions where image_doc_id points to any image in this repo
203
+ cursor.execute(
204
+ f"""
205
+ DELETE FROM {self.SESSIONS_TABLE}
206
+ WHERE image_doc_id IN (
207
+ SELECT id FROM {self.IMAGES_TABLE} WHERE repo_id = ?
208
+ )
209
+ """,
210
+ (repo_id,),
211
+ )
212
+
213
+ # Delete all images from this repo
214
+ cursor.execute(
215
+ f"DELETE FROM {self.IMAGES_TABLE} WHERE repo_id = ?",
216
+ (repo_id,),
217
+ )
218
+
219
+ # Finally delete the repo itself
220
+ cursor.execute(f"DELETE FROM {self.REPOS_TABLE} WHERE id = ?", (repo_id,))
221
+
222
+ self._db.commit()
223
+
224
+ def upsert_repo(self, url: str) -> int:
225
+ """Insert or update a repo record by unique URL.
226
+
227
+ Args:
228
+ url: The repository URL (e.g., 'file:///path/to/repo')
229
+
230
+ Returns:
231
+ The rowid of the inserted/updated record.
232
+ """
233
+ cursor = self._db.cursor()
234
+ cursor.execute(
235
+ f"""
236
+ INSERT INTO {self.REPOS_TABLE} (url) VALUES (?)
237
+ ON CONFLICT(url) DO NOTHING
238
+ """,
239
+ (url,),
240
+ )
241
+
242
+ self._db.commit()
243
+
244
+ # Get the rowid of the inserted/existing record
245
+ cursor.execute(f"SELECT id FROM {self.REPOS_TABLE} WHERE url = ?", (url,))
246
+ result = cursor.fetchone()
247
+ if result:
248
+ return result[0]
249
+ return cursor.lastrowid if cursor.lastrowid is not None else 0
250
+
251
+ def get_repo_id(self, url: str) -> int | None:
252
+ """Get the repo_id for a given URL.
253
+
254
+ Args:
255
+ url: The repository URL
256
+
257
+ Returns:
258
+ The repo_id if found, None otherwise
259
+ """
260
+ cursor = self._db.cursor()
261
+ cursor.execute(f"SELECT id FROM {self.REPOS_TABLE} WHERE url = ?", (url,))
262
+ result = cursor.fetchone()
263
+ return result[0] if result else None
264
+
265
+ def get_repo_url(self, repo_id: int) -> str | None:
266
+ """Get the URL for a given repo_id.
267
+
268
+ Args:
269
+ repo_id: The repository ID
270
+
271
+ Returns:
272
+ The URL if found, None otherwise
273
+ """
274
+ cursor = self._db.cursor()
275
+ cursor.execute(f"SELECT url FROM {self.REPOS_TABLE} WHERE id = ?", (repo_id,))
276
+ result = cursor.fetchone()
277
+ return result[0] if result else None
278
+
125
279
  # --- Convenience helpers for common image operations ---
126
- def upsert_image(self, record: dict[str, Any]) -> int:
280
+ def upsert_image(self, record: dict[str, Any], repo_url: str) -> int:
127
281
  """Insert or update an image record by unique path.
128
282
 
129
- The record must include a 'path' key; other keys are arbitrary FITS metadata.
283
+ The record must include a 'path' key (relative to repo); other keys are arbitrary FITS metadata.
284
+ The path is stored as-is - caller is responsible for making it relative to the repo.
130
285
  DATE-OBS and DATE are extracted and stored as indexed columns for efficient queries.
131
- Returns the rowid of the inserted/updated record.
286
+
287
+ Args:
288
+ record: Dictionary containing image metadata including 'path' (relative to repo)
289
+ repo_url: The repository URL this image belongs to
290
+
291
+ Returns:
292
+ The rowid of the inserted/updated record.
132
293
  """
133
294
  path = record.get("path")
134
295
  if not path:
135
296
  raise ValueError("record must include 'path'")
136
297
 
298
+ # Get or create the repo_id for this URL
299
+ repo_id = self.get_repo_id(repo_url)
300
+ if repo_id is None:
301
+ repo_id = self.upsert_repo(repo_url)
302
+
137
303
  # Extract date fields for column storage
138
304
  date_obs = record.get(self.DATE_OBS_KEY)
139
305
  date = record.get(self.DATE_KEY)
@@ -145,25 +311,28 @@ class Database:
145
311
  cursor = self._db.cursor()
146
312
  cursor.execute(
147
313
  f"""
148
- INSERT INTO {self.IMAGES_TABLE} (path, date_obs, date, metadata) VALUES (?, ?, ?, ?)
149
- ON CONFLICT(path) DO UPDATE SET
314
+ INSERT INTO {self.IMAGES_TABLE} (repo_id, path, date_obs, date, metadata) VALUES (?, ?, ?, ?, ?)
315
+ ON CONFLICT(repo_id, path) DO UPDATE SET
150
316
  date_obs = excluded.date_obs,
151
317
  date = excluded.date,
152
318
  metadata = excluded.metadata
153
319
  """,
154
- (path, date_obs, date, metadata_json),
320
+ (repo_id, str(path), date_obs, date, metadata_json),
155
321
  )
156
322
 
157
323
  self._db.commit()
158
324
 
159
325
  # Get the rowid of the inserted/updated record
160
- cursor.execute(f"SELECT id FROM {self.IMAGES_TABLE} WHERE path = ?", (path,))
326
+ cursor.execute(
327
+ f"SELECT id FROM {self.IMAGES_TABLE} WHERE repo_id = ? AND path = ?",
328
+ (repo_id, str(path)),
329
+ )
161
330
  result = cursor.fetchone()
162
331
  if result:
163
332
  return result[0]
164
333
  return cursor.lastrowid if cursor.lastrowid is not None else 0
165
334
 
166
- def search_image(self, conditions: dict[str, Any]) -> list[dict[str, Any]] | None:
335
+ def search_image(self, conditions: dict[str, Any]) -> list[SessionRow]:
167
336
  """Search for images matching the given conditions.
168
337
 
169
338
  Args:
@@ -173,7 +342,7 @@ class Database:
173
342
  - 'date_end': Filter images with DATE-OBS <= this date
174
343
 
175
344
  Returns:
176
- List of matching image records or None if no matches
345
+ List of matching image records with relative path, repo_id, and repo_url
177
346
  """
178
347
  # Extract special date filter keys (make a copy to avoid modifying caller's dict)
179
348
  conditions_copy = dict(conditions)
@@ -185,15 +354,19 @@ class Database:
185
354
  params = []
186
355
 
187
356
  if date_start:
188
- where_clauses.append("date_obs >= ?")
357
+ where_clauses.append("i.date_obs >= ?")
189
358
  params.append(date_start)
190
359
 
191
360
  if date_end:
192
- where_clauses.append("date_obs <= ?")
361
+ where_clauses.append("i.date_obs <= ?")
193
362
  params.append(date_end)
194
363
 
195
- # Build the query
196
- query = f"SELECT id, path, date_obs, date, metadata FROM {self.IMAGES_TABLE}"
364
+ # Build the query with JOIN to repos table
365
+ query = f"""
366
+ SELECT i.id, i.repo_id, i.path, i.date_obs, i.date, i.metadata, r.url as repo_url
367
+ FROM {self.IMAGES_TABLE} i
368
+ JOIN {self.REPOS_TABLE} r ON i.repo_id = r.id
369
+ """
197
370
  if where_clauses:
198
371
  query += " WHERE " + " AND ".join(where_clauses)
199
372
 
@@ -203,7 +376,10 @@ class Database:
203
376
  results = []
204
377
  for row in cursor.fetchall():
205
378
  metadata = json.loads(row["metadata"])
379
+ # Store the relative path, repo_id, and repo_url for caller
206
380
  metadata["path"] = row["path"]
381
+ metadata["repo_id"] = row["repo_id"]
382
+ metadata["repo_url"] = row["repo_url"]
207
383
  metadata["id"] = row["id"]
208
384
 
209
385
  # Add date fields back to metadata for compatibility
@@ -218,11 +394,11 @@ class Database:
218
394
  if match:
219
395
  results.append(metadata)
220
396
 
221
- return results if results else None
397
+ return results
222
398
 
223
399
  def search_session(
224
- self, conditions: dict[str, Any] | None
225
- ) -> list[dict[str, Any]] | None:
400
+ self, where_tuple: tuple[str, list[Any]] = ("", [])
401
+ ) -> list[SessionRow]:
226
402
  """Search for sessions matching the given conditions.
227
403
 
228
404
  Args:
@@ -232,62 +408,32 @@ class Database:
232
408
  - 'date_end': Filter sessions starting on or before this date
233
409
 
234
410
  Returns:
235
- List of matching session records or None
411
+ List of matching session records with metadata from the reference image
236
412
  """
237
- if conditions is None:
238
- return self.all_sessions()
239
-
240
- cursor = self._db.cursor()
241
- cursor.execute(
242
- f"""
243
- SELECT id, start, end, filter, imagetyp, object, telescop,
244
- num_images, exptime_total, image_doc_id
245
- FROM {self.SESSIONS_TABLE}
413
+ # Build WHERE clause dynamically based on conditions
414
+ where_clause, params = where_tuple
415
+
416
+ # Build the query with JOIN to images table to get reference image metadata
417
+ query = f"""
418
+ SELECT s.id, s.start, s.end, s.filter, s.imagetyp, s.object, s.telescop,
419
+ s.num_images, s.exptime_total, s.image_doc_id, i.metadata
420
+ FROM {self.SESSIONS_TABLE} s
421
+ LEFT JOIN {self.IMAGES_TABLE} i ON s.image_doc_id = i.id
422
+ {where_clause}
246
423
  """
247
- )
248
-
249
- # Extract date range conditions if present
250
- date_start = conditions.get("date_start")
251
- date_end = conditions.get("date_end")
252
424
 
253
- # Create a copy without date range keys for standard matching
254
- standard_conditions = {
255
- k: v
256
- for k, v in conditions.items()
257
- if k not in ("date_start", "date_end") and v is not None
258
- }
425
+ cursor = self._db.cursor()
426
+ cursor.execute(query, params)
259
427
 
260
428
  results = []
261
429
  for row in cursor.fetchall():
262
- session = {
263
- "id": row["id"],
264
- self.START_KEY: row["start"],
265
- self.END_KEY: row["end"],
266
- self.FILTER_KEY: row["filter"],
267
- self.IMAGETYP_KEY: row["imagetyp"],
268
- self.OBJECT_KEY: row["object"],
269
- self.TELESCOP_KEY: row["telescop"],
270
- self.NUM_IMAGES_KEY: row["num_images"],
271
- self.EXPTIME_TOTAL_KEY: row["exptime_total"],
272
- self.IMAGE_DOC_KEY: row["image_doc_id"],
273
- }
274
-
275
- # Check if all standard conditions match
276
- match = all(session.get(k) == v for k, v in standard_conditions.items())
277
-
278
- # Apply date range filtering
279
- if match and date_start:
280
- session_start = session.get(self.START_KEY, "")
281
- match = match and session_start >= date_start
282
-
283
- if match and date_end:
284
- session_start = session.get(self.START_KEY, "")
285
- match = match and session_start <= date_end
286
-
287
- if match:
288
- results.append(session)
430
+ session_dict = dict(row)
431
+ # Parse the metadata JSON if it exists
432
+ if session_dict.get("metadata"):
433
+ session_dict["metadata"] = json.loads(session_dict["metadata"])
434
+ results.append(session_dict)
289
435
 
290
- return results if results else None
436
+ return results
291
437
 
292
438
  def len_table(self, table_name: str) -> int:
293
439
  """Return the total number of rows in the specified table."""
@@ -314,20 +460,35 @@ class Database:
314
460
  result = cursor.fetchone()
315
461
  return result[0] if result and result[0] is not None else 0
316
462
 
317
- def get_image(self, path: str) -> dict[str, Any] | None:
318
- """Get an image record by path."""
463
+ def get_image(self, repo_url: str, path: str) -> ImageRow | None:
464
+ """Get an image record by repo_url and relative path.
465
+
466
+ Args:
467
+ repo_url: The repository URL
468
+ path: Path relative to the repository root
469
+
470
+ Returns:
471
+ Image record with relative path, repo_id, and repo_url, or None if not found
472
+ """
319
473
  cursor = self._db.cursor()
320
474
  cursor.execute(
321
- f"SELECT id, path, date_obs, date, metadata FROM {self.IMAGES_TABLE} WHERE path = ?",
322
- (path,),
475
+ f"""
476
+ SELECT i.id, i.repo_id, i.path, i.date_obs, i.date, i.metadata, r.url as repo_url
477
+ FROM {self.IMAGES_TABLE} i
478
+ JOIN {self.REPOS_TABLE} r ON i.repo_id = r.id
479
+ WHERE r.url = ? AND i.path = ?
480
+ """,
481
+ (repo_url, path),
323
482
  )
324
- row = cursor.fetchone()
325
483
 
484
+ row = cursor.fetchone()
326
485
  if row is None:
327
486
  return None
328
487
 
329
488
  metadata = json.loads(row["metadata"])
330
489
  metadata["path"] = row["path"]
490
+ metadata["repo_id"] = row["repo_id"]
491
+ metadata["repo_url"] = row["repo_url"]
331
492
  metadata["id"] = row["id"]
332
493
 
333
494
  # Add date fields back to metadata for compatibility
@@ -338,17 +499,24 @@ class Database:
338
499
 
339
500
  return metadata
340
501
 
341
- def all_images(self) -> list[dict[str, Any]]:
342
- """Return all image records."""
502
+ def all_images(self) -> list[ImageRow]:
503
+ """Return all image records with relative paths, repo_id, and repo_url."""
343
504
  cursor = self._db.cursor()
344
505
  cursor.execute(
345
- f"SELECT id, path, date_obs, date, metadata FROM {self.IMAGES_TABLE}"
506
+ f"""
507
+ SELECT i.id, i.repo_id, i.path, i.date_obs, i.date, i.metadata, r.url as repo_url
508
+ FROM {self.IMAGES_TABLE} i
509
+ JOIN {self.REPOS_TABLE} r ON i.repo_id = r.id
510
+ """
346
511
  )
347
512
 
348
513
  results = []
349
514
  for row in cursor.fetchall():
350
515
  metadata = json.loads(row["metadata"])
516
+ # Return relative path, repo_id, and repo_url for caller
351
517
  metadata["path"] = row["path"]
518
+ metadata["repo_id"] = row["repo_id"]
519
+ metadata["repo_url"] = row["repo_url"]
352
520
  metadata["id"] = row["id"]
353
521
 
354
522
  # Add date fields back to metadata for compatibility
@@ -361,35 +529,6 @@ class Database:
361
529
 
362
530
  return results
363
531
 
364
- def all_sessions(self) -> list[dict[str, Any]]:
365
- """Return all session records."""
366
- cursor = self._db.cursor()
367
- cursor.execute(
368
- f"""
369
- SELECT id, start, end, filter, imagetyp, object, telescop,
370
- num_images, exptime_total, image_doc_id
371
- FROM {self.SESSIONS_TABLE}
372
- """
373
- )
374
-
375
- results = []
376
- for row in cursor.fetchall():
377
- session = {
378
- "id": row["id"],
379
- self.START_KEY: row["start"],
380
- self.END_KEY: row["end"],
381
- self.FILTER_KEY: row["filter"],
382
- self.IMAGETYP_KEY: row["imagetyp"],
383
- self.OBJECT_KEY: row["object"],
384
- self.TELESCOP_KEY: row["telescop"],
385
- self.NUM_IMAGES_KEY: row["num_images"],
386
- self.EXPTIME_TOTAL_KEY: row["exptime_total"],
387
- self.IMAGE_DOC_KEY: row["image_doc_id"],
388
- }
389
- results.append(session)
390
-
391
- return results
392
-
393
532
  def get_session_by_id(self, session_id: int) -> dict[str, Any] | None:
394
533
  """Get a session record by its ID.
395
534
 
@@ -414,20 +553,9 @@ class Database:
414
553
  if row is None:
415
554
  return None
416
555
 
417
- return {
418
- "id": row["id"],
419
- self.START_KEY: row["start"],
420
- self.END_KEY: row["end"],
421
- self.FILTER_KEY: row["filter"],
422
- self.IMAGETYP_KEY: row["imagetyp"],
423
- self.OBJECT_KEY: row["object"],
424
- self.TELESCOP_KEY: row["telescop"],
425
- self.NUM_IMAGES_KEY: row["num_images"],
426
- self.EXPTIME_TOTAL_KEY: row["exptime_total"],
427
- self.IMAGE_DOC_KEY: row["image_doc_id"],
428
- }
429
-
430
- def get_session(self, to_find: dict[str, str]) -> dict[str, Any] | None:
556
+ return dict(row)
557
+
558
+ def get_session(self, to_find: dict[str, str]) -> SessionRow | None:
431
559
  """Find a session matching the given criteria.
432
560
 
433
561
  Searches for sessions with the same filter, image type, target, and telescope
@@ -470,21 +598,10 @@ class Database:
470
598
  if row is None:
471
599
  return None
472
600
 
473
- return {
474
- "id": row["id"],
475
- self.START_KEY: row["start"],
476
- self.END_KEY: row["end"],
477
- self.FILTER_KEY: row["filter"],
478
- self.IMAGETYP_KEY: row["imagetyp"],
479
- self.OBJECT_KEY: row["object"],
480
- self.TELESCOP_KEY: row["telescop"],
481
- self.NUM_IMAGES_KEY: row["num_images"],
482
- self.EXPTIME_TOTAL_KEY: row["exptime_total"],
483
- self.IMAGE_DOC_KEY: row["image_doc_id"],
484
- }
601
+ return dict(row)
485
602
 
486
603
  def upsert_session(
487
- self, new: dict[str, Any], existing: dict[str, Any] | None = None
604
+ self, new: SessionRow, existing: SessionRow | None = None
488
605
  ) -> None:
489
606
  """Insert or update a session record."""
490
607
  cursor = self._db.cursor()
@@ -3,6 +3,23 @@
3
3
  [repo]
4
4
  kind = "preferences"
5
5
 
6
+ [aliases]
7
+ # aliases can be used to map non standard (or non english) frame names to standard terms
8
+ # This is also used to map filters based on common misspellings or variations.
9
+ # We assume the first listed option in the list is the 'canonical' name used for printing etc...
10
+
11
+ # frame types
12
+ dark = ["dark", "darks"]
13
+ flat = ["flat", "flats"]
14
+ bias = ["bias", "biases"]
15
+
16
+ # file suffixes
17
+ fit = ["fits", "fit"]
18
+
19
+ # filter names
20
+ SiiOiii = ["SiiOiii", "SII-OIII", "S2-O3"]
21
+ HaOiii = ["HaOiii", "HA-OIII", "Halpha-O3"]
22
+
6
23
  # FIXME, somewhere here list default patterns which can be used to identify NINA, ASIAIR, SEESTAR
7
24
  # raw repo layouts
8
25
 
starbash/main.py CHANGED
@@ -6,7 +6,7 @@ import starbash.url as url
6
6
  import starbash
7
7
 
8
8
  from .app import Starbash, get_user_config_path, setup_logging
9
- from .commands import info, repo, select, user
9
+ from .commands import info, process, repo, select, user
10
10
  from . import console
11
11
 
12
12
  app = typer.Typer(
@@ -17,6 +17,9 @@ app.add_typer(user.app, name="user", help="Manage user settings.")
17
17
  app.add_typer(repo.app, name="repo", help="Manage Starbash repositories.")
18
18
  app.add_typer(select.app, name="select", help="Manage session and target selection.")
19
19
  app.add_typer(info.app, name="info", help="Display system and data information.")
20
+ app.add_typer(
21
+ process.app, name="process", help="Process images using automated workflows."
22
+ )
20
23
 
21
24
 
22
25
  @app.callback(invoke_without_command=True)