Coverage for src/susi/db/metadata.py: 97%
79 statements
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2025-06-13 14:15 +0000
1from __future__ import annotations
3import os
4import pickle
5import re
6from typing import Union
8from ..base import Logging
9from ..base.header_keys import *
10from ..io import Fits
12log = Logging.get_logger()
15class Metadata:
16 """
17 Metadata database based on pickel files that contain a python dict.
18 """
20 DB_KEYS = [
21 DATE_OBS,
22 TIMESTAMP_US,
23 CAMERA_NAME,
24 CAMERA_ID,
25 INTEGRATION_TIME,
26 HK_PMU_CUR,
27 HK_PMU_ANG,
28 HK_PMU_ANG_ERR,
29 MOD_STATE,
30 IMG_RMS,
31 IMG_SNR,
32 IMG_MEAN,
33 IMG_CONTRAST,
34 ADC15_CF_TEMP,
35 ROI_X0,
36 ROI_X1,
37 ROI_Y0,
38 ROI_Y1,
39 TEM7,
40 SHEAR_CORR,
41 ROTATION_ANG,
42 XCEN,
43 YCEN,
44 CW_LOOP,
45 FLAT_MOD,
46 PS_STATE,
47 SLIT_FLAT_OFFSET,
48 SLIT_FLAT_SLOPE,
49 ]
51 @staticmethod
52 def insert(fits: Fits, overwrite=False) -> None:
53 """
54 Insert the metadata from the given fits file to the appropriate database.
56 The database is selected by the full path.
57 For each hourly folder there should be a database file with a matching name.
59 All selected metadata is added to the database and is committed (saved) afterwards automatically
60 """
61 db = Metadata.db_path(fits)
62 if db is not None:
63 Metadata(db).add(fits, overwrite=overwrite).commit()
65 @staticmethod
66 def insert_batch(fitses: list, overwrite=False) -> None:
67 """
68 Insert the metadata from the given list of fits file to the appropriate database.
69 Note The list is assumed to be sorted from oldest to newest!
71 The database is selected by the full path of the first fits file.
72 For each hourly folder there should be a database file with a matching name.
74 All selected metadata is added to the database and is committed (saved) afterwards automatically
75 """
76 db0 = Metadata.db_path(fitses[0])
77 db1 = Metadata.db_path(fitses[1])
78 if db0 is None or db0 != db1:
79 raise ValueError('Cannot save the metadata of the batch to a single DB file...')
81 db = Metadata(db0)
82 for file in fitses:
83 db.add(file, overwrite=overwrite)
84 db.commit()
86 @staticmethod
87 def db_path(fits: Union[Fits, str]) -> Union[str, None]:
88 """
89 Get the matching metadata file for the given fits
91 The database is selected by the full path.
92 For each hourly folder there should be a database file with a matching name.
93 """
94 path = fits.path if isinstance(fits, Fits) else fits
95 m = re.match(r".*(\d{4}_\d{2}_\d{2}_\d{2}).*(cam\d).*", path)
96 if m:
97 filename = f'{m.group(1)}-{m.group(2)}.pickle'
98 return os.path.join(path.split(m.group(1))[0], filename)
100 m = re.match(r".*(\d{4}_\d{2}_\d{2}_\d{2}).*", path)
101 if m:
102 filename = f'{m.group(1)}.pickle'
103 return os.path.join(path.split(m.group(1))[0], filename)
105 log.error('Could not determine database by fits file name %s', path)
106 return None
108 def __init__(self, path: str):
109 self.changed = False
110 self.path = path
111 self.data = {}
112 self.reload()
114 def reload(self) -> None:
115 if not os.path.isfile(self.path):
116 return
118 with open(self.path, 'rb') as f:
119 self.data = pickle.load(f)
121 def __contains__(self, file: str) -> bool:
122 """Check if a given entry is in the database"""
123 return os.path.basename(file) in self.data
125 def __getitem__(self, file: str) -> Union[dict, None]:
126 key = os.path.basename(file)
127 return self.data[key] if key in self else None
129 def add(self, fits: Fits, overwrite=False) -> Metadata:
130 """Add an entry to the metadata database"""
131 key = os.path.basename(fits.path)
132 if overwrite or key not in self:
133 self.changed = True
134 self.data[key] = Metadata._generate_entry(fits)
135 return self
137 def delete(self, file: Union[Fits, str]):
138 """Remove an entry from the metadata database"""
139 path = file.path if isinstance(file, Fits) else file
140 self.data.pop(os.path.basename(path), None)
142 def commit(self) -> None:
143 """Write all changes (if any) back to disk"""
144 if not self.changed:
145 return
147 os.makedirs(os.path.dirname(self.path), exist_ok=True)
148 with open(self.path, 'wb', 0o775) as f:
149 pickle.dump(self.data, f)
150 self.changed = False
152 @staticmethod
153 def _generate_entry(fits: Fits):
154 entry = {}
155 for key in Metadata.DB_KEYS:
156 try:
157 entry[key] = fits.header[key]
158 except KeyError:
159 entry[key] = None
160 return entry