Coverage for src/susi/db/metadata.py: 91%
86 statements
« prev ^ index » next coverage.py v7.5.0, created at 2025-08-22 09:20 +0000
« prev ^ index » next coverage.py v7.5.0, created at 2025-08-22 09:20 +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...')
80 db = Metadata(db0)
81 for file in fitses:
82 db.add(file, overwrite=overwrite)
83 db.commit()
85 @staticmethod
86 def db_path(fits: Union[Fits, str]) -> Union[str, None]:
87 """
88 Get the matching metadata file for the given fits
90 The database is selected by the full path.
91 For each hourly folder there should be a database file with a matching name.
92 """
93 path = fits.path if isinstance(fits, Fits) else fits
94 m = re.match(r".*(\d{4}_\d{2}_\d{2}_\d{2}).*(cam\d).*", path)
95 if m:
96 filename = f'{m.group(1)}-{m.group(2)}.pickle'
97 return os.path.join(path.split(m.group(1))[0], filename)
99 m = re.match(r".*(\d{4}_\d{2}_\d{2}_\d{2}).*", path)
100 if m:
101 filename = f'{m.group(1)}.pickle'
102 return os.path.join(path.split(m.group(1))[0], filename)
104 log.error('Could not determine database by fits file name %s', path)
105 return None
107 def __init__(self, path: str):
108 self.changed = False
109 self.path = path
110 self.data = {}
111 self.reload()
113 def reload(self) -> None:
114 if not os.path.isfile(self.path):
115 return
117 try:
118 with open(self.path, 'rb') as f:
119 self.data = pickle.load(f)
120 except (pickle.UnpicklingError, EOFError, FileNotFoundError) as e:
121 log.error("Error loading metadata file %s: %s", self.path, e)
122 log.error("Deleting corrupted file and creating a new one.")
123 os.remove(self.path)
124 self.data = {}
125 return
127 def __contains__(self, file: str) -> bool:
128 """Check if a given entry is in the database"""
129 return os.path.basename(file) in self.data
131 def __getitem__(self, file: str) -> Union[dict, None]:
132 key = os.path.basename(file)
133 return self.data[key] if key in self else None
135 def add(self, fits: Fits, overwrite=False) -> Metadata:
136 """Add an entry to the metadata database"""
137 key = os.path.basename(fits.path)
138 if overwrite or key not in self:
139 self.changed = True
140 self.data[key] = Metadata._generate_entry(fits)
141 return self
143 def delete(self, file: Union[Fits, str]):
144 """Remove an entry from the metadata database"""
145 path = file.path if isinstance(file, Fits) else file
146 self.data.pop(os.path.basename(path), None)
148 def commit(self) -> None:
149 """Write all changes (if any) back to disk"""
150 if not self.changed:
151 return
153 os.makedirs(os.path.dirname(self.path), exist_ok=True)
154 with open(self.path, 'wb', 0o775) as f:
155 pickle.dump(self.data, f)
156 self.changed = False
158 @staticmethod
159 def _generate_entry(fits: Fits):
160 entry = {}
161 for key in Metadata.DB_KEYS:
162 try:
163 entry[key] = fits.header[key]
164 except KeyError:
165 entry[key] = None
166 return entry