corgidrp 0.1__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.
corgidrp/__init__.py ADDED
@@ -0,0 +1,63 @@
1
+ import configparser
2
+ import os
3
+ import pathlib
4
+ import configparser
5
+
6
+ __version__ = "0.1"
7
+ version = __version__ # temporary backwards compatability
8
+
9
+ #### Create a configuration file for the corgidrp if it doesn't exist.
10
+ def create_config_dir():
11
+ """
12
+ Checks if the default .corgidrp directory exists, and if not, it sets it up
13
+ """
14
+ homedir = pathlib.Path.home()
15
+ config_folder = os.path.join(homedir, ".corgidrp")
16
+ # replace legacy file with folder if needed
17
+ if os.path.isfile(config_folder):
18
+ oldconfig = configparser.ConfigParser()
19
+ oldconfig.read(config_folder)
20
+ os.remove(config_folder)
21
+ else:
22
+ oldconfig = None
23
+
24
+ # make folder if doesn't exist
25
+ if not os.path.isdir(config_folder):
26
+ os.mkdir(config_folder)
27
+
28
+ # make default calibrations folder
29
+ default_cal_dir = os.path.join(config_folder, "default_calibs")
30
+ if not os.path.exists(default_cal_dir):
31
+ os.mkdir(default_cal_dir)
32
+
33
+ # write config
34
+ config_filepath = os.path.join(config_folder, "corgidrp.cfg")
35
+ if not os.path.exists(config_filepath):
36
+ config = configparser.ConfigParser()
37
+ config["PATH"] = {}
38
+ config["PATH"]["caldb"] = os.path.join(config_folder, "corgidrp_caldb.csv") # location to store caldb
39
+ config["PATH"]["default_calibs"] = default_cal_dir
40
+ config["DATA"] = {}
41
+ config["DATA"]["track_individual_errors"] = "False"
42
+ # overwrite with old settings if needed
43
+ if oldconfig is not None:
44
+ config["PATH"]["caldb"] = oldconfig["PATH"]["caldb"]
45
+
46
+ with open(config_filepath, 'w') as f:
47
+ config.write(f)
48
+
49
+ print("corgidrp: Configuration file written to {0}. Please edit if you want things stored in different locations.".format(config_filepath))
50
+ create_config_dir()
51
+
52
+ _bool_map = {"true" : True, "false" : False}
53
+
54
+ # borrowed from the kpicdrp caldb
55
+ # load in default caldbs based on configuration file
56
+ config_filepath = os.path.join(pathlib.Path.home(), ".corgidrp", "corgidrp.cfg")
57
+ config = configparser.ConfigParser()
58
+ config.read(config_filepath)
59
+
60
+ ## pipeline settings
61
+ caldb_filepath = config.get("PATH", "caldb", fallback=None)
62
+ default_cal_dir = config.get("PATH", "default_calibs", fallback=None)
63
+ track_individual_errors = _bool_map[config.get("DATA", "track_individual_errors").lower()]
corgidrp/caldb.py ADDED
@@ -0,0 +1,304 @@
1
+ """
2
+ Calibration tracking system. Modified from kpicdrp caldb implmentation (Copyright (c) 2024, KPIC Team)
3
+ """
4
+ import os
5
+ import numpy as np
6
+ import pandas as pd
7
+ import corgidrp
8
+ import corgidrp.data as data
9
+ import astropy.time as time
10
+
11
+
12
+ column_names = [
13
+ "Filepath",
14
+ "Type",
15
+ "MJD",
16
+ "EXPTIME",
17
+ "Files Used",
18
+ "Date Created",
19
+ "Hash",
20
+ "DRPVERSN",
21
+ "OBSID",
22
+ "NAXIS1",
23
+ "NAXIS2",
24
+ "OPMODE",
25
+ "CMDGAIN",
26
+ "EXCAMT",
27
+ ]
28
+
29
+ labels = {data.Dark: "Dark",
30
+ data.NonLinearityCalibration: "NonLinearityCalibration",
31
+ data.BadPixelMap: "BadPixelMap",
32
+ data.KGain : "KGain",
33
+ data.DetectorParams : "DetectorParams"}
34
+
35
+ class CalDB:
36
+ """
37
+ Database for tracking calibration files saved to disk. Modified from the kpicdrp version
38
+
39
+ Note that database is not parallelism-safe, but should be ok in most cases.
40
+ (Jason: look at using posix_ipc to guarantee thread safety if we really need it)
41
+
42
+ Args:
43
+ filepath (str): [optional] filepath to a CSV file with an existing database
44
+
45
+ Fields:
46
+ columns (list): column names of dataframe
47
+ filepath(str): full filepath to data
48
+ """
49
+
50
+ def __init__(self, filepath=""):
51
+ """
52
+ Args:
53
+ filepath (str): [optional] filepath to a CSV file with an existing database
54
+ """
55
+
56
+ # If filepath is not passed in, use the default (majority of case)
57
+ if len(filepath) == 0:
58
+ self.filepath = corgidrp.caldb_filepath
59
+ else:
60
+ # possibly edge case where we want to use specialized caldb
61
+ self.filepath = filepath
62
+
63
+ # if database does't exist, create a blank one
64
+ if not os.path.exists(self.filepath):
65
+ # new database
66
+ self.columns = column_names
67
+ self._db = pd.DataFrame(columns=self.columns)
68
+ self.save()
69
+ else:
70
+ # already a database exists
71
+ self.load()
72
+ self.columns = list(self._db.columns.values)
73
+
74
+ def load(self):
75
+ """
76
+ Load/update db from filepath
77
+ """
78
+ self._db = pd.read_csv(self.filepath)
79
+
80
+ def save(self):
81
+ """
82
+ Save file without numbered index to disk with user specified filepath as a CSV file
83
+ """
84
+ self._db.to_csv(self.filepath, index=False)
85
+
86
+ def _get_values_from_entry(self, entry, is_calib=True):
87
+ """
88
+ Extract the properties from this data entry to ingest them into the database
89
+
90
+ Args:
91
+ entry (corgidrp.data.Image subclass): calibration frame to add to the database
92
+ is_calib (bool): is a calibration frame. if Not, it won't look up filetype.
93
+ Only used in get_calib() to grab metadata for science frames
94
+
95
+ Returns:
96
+ tuple:
97
+ row (list):
98
+ List of data entry properties
99
+ row_dict (dict):
100
+ Dictionary of data entry properties keyed by column names
101
+
102
+ """
103
+ filepath = os.path.abspath(entry.filepath)
104
+ if is_calib:
105
+ datatype = labels[entry.__class__] # get the database str representation
106
+ else:
107
+ datatype = "Sci"
108
+ mjd = time.Time(entry.ext_hdr["SCTSRT"]).mjd
109
+ exptime = entry.ext_hdr["EXPTIME"]
110
+
111
+ # check if this exists. will be a keyword written by corgidrp
112
+ if "DRPNFILE" in entry.ext_hdr:
113
+ files_used = entry.ext_hdr["DRPNFILE"]
114
+ else:
115
+ files_used = 0
116
+
117
+ if "DRPCTIME" in entry.ext_hdr:
118
+ date_created = time.Time(entry.ext_hdr["DRPCTIME"]).mjd
119
+ else:
120
+ date_created = -1
121
+
122
+ if "DRPVERSN" in entry.ext_hdr:
123
+ drp_version = entry.ext_hdr["DRPVERSN"]
124
+ else:
125
+ drp_version = ""
126
+
127
+ obsid = entry.pri_hdr["OBSID"]
128
+
129
+ hash_val = entry.get_hash()
130
+
131
+ # this only works for 2D images. may need to adapt for non-2D calibration frames
132
+ naxis1 = entry.data.shape[-1]
133
+ naxis2 = entry.data.shape[-2]
134
+
135
+ row = [
136
+ filepath,
137
+ datatype,
138
+ mjd,
139
+ exptime,
140
+ files_used,
141
+ date_created,
142
+ hash_val,
143
+ drp_version,
144
+ obsid,
145
+ naxis1,
146
+ naxis2,
147
+ ]
148
+
149
+ # rest are ext_hdr keys we can copy
150
+ start_index = len(row)
151
+ for i in range(start_index, len(self.columns)):
152
+ row.append(entry.ext_hdr[self.columns[i]]) # add value staright from header
153
+
154
+ row_dict = {}
155
+ for key, val in zip(self.columns, row):
156
+ row_dict[key] = val
157
+
158
+ return row, row_dict
159
+
160
+ def create_entry(self, entry, to_disk=True):
161
+ """
162
+ Add a new entry to or update an existing one in the database. Note that function by default will load and save db to disk
163
+
164
+ Args:
165
+ entry (corgidrp.data.Image subclass): calibration frame to add to the database
166
+ to_disk (bool): True by default, will update DB from disk before adding entry and saving it back to disk
167
+ """
168
+ new_row, row_dict = self._get_values_from_entry(entry)
169
+
170
+ # update database from disk in case anything changed
171
+ if to_disk:
172
+ self.load()
173
+
174
+ # use filepath as key to see if it's already in database
175
+ if row_dict["Filepath"] in self._db.values:
176
+ row_index = self._db[
177
+ self._db["Filepath"] == row_dict["Filepath"]
178
+ ].index.values
179
+ self._db.loc[row_index, self.columns] = new_row
180
+ # otherwise create new entry
181
+ else:
182
+ new_entry = pd.DataFrame([new_row], columns=self.columns)
183
+ self._db = pd.concat([self._db, new_entry], ignore_index=True)
184
+
185
+ # save to disk to update changes
186
+ if to_disk:
187
+ self.save()
188
+
189
+ def remove_entry(self, entry, to_disk=True):
190
+ """
191
+ Remove an entry from the database. Removes the entire row
192
+
193
+ Args:
194
+ entry (corgidrp.data.Image subclass): calibration frame to add to the database
195
+ to_disk (bool): True by default, will update DB from disk before adding entry and saving it back to disk
196
+ """
197
+ new_row, row_dict = self._get_values_from_entry(entry)
198
+
199
+ # update database from disk in case anything changed
200
+ if to_disk:
201
+ self.load()
202
+
203
+ if row_dict["Filepath"] in self._db.values:
204
+ entry_index = self._db[
205
+ self._db["Filepath"] == row_dict["Filepath"]
206
+ ].index.values
207
+ self._db = self._db.drop(self._db.index[entry_index])
208
+ self._db = self._db.reset_index(drop=True)
209
+ else:
210
+ raise ValueError("No filepath found so could not remove.")
211
+
212
+ # save to disk to update changes
213
+ if to_disk:
214
+ self.save()
215
+
216
+ def get_calib(self, frame, dtype, to_disk=True):
217
+ """
218
+ Outputs the best calibration file of the given type for the input sciene frame.
219
+
220
+ Args:
221
+ frame (corgidrp.data.Image): an image frame to request a calibratio for
222
+ dtype (corgidrp.data Class): for example: corgidrp.data.Dark (TODO: document the entire list of options)
223
+ to_disk (bool): True by default, will update DB from disk before matching
224
+
225
+ Returns:
226
+ corgidrp.data.*: an instance of the appropriate calibration type (Exact type depends on calibration type)
227
+ """
228
+ if dtype not in labels:
229
+ raise ValueError(
230
+ "Requested calibration dtype of {0} not a valid option".format(dtype)
231
+ )
232
+ dtype_label = labels[dtype]
233
+
234
+ # get values for this science frame
235
+ _, frame_dict = self._get_values_from_entry(frame, is_calib=False)
236
+
237
+ # update database from disk in case anything changed
238
+ if to_disk:
239
+ self.load()
240
+
241
+ # downselect to only calibs of this type
242
+ calibdf = self._db[self._db["Type"] == dtype_label]
243
+
244
+ if dtype_label in ["Dark"]:
245
+ # general selection criteria for 2D image frames. Can use different selection criteria for different dtypes
246
+ options = calibdf.loc[
247
+ (
248
+ (calibdf["EXPTIME"] == frame_dict["EXPTIME"])
249
+ & (calibdf["NAXIS1"] == frame_dict["NAXIS1"])
250
+ & (calibdf["NAXIS2"] == frame_dict["NAXIS2"])
251
+ )
252
+ ]
253
+ else:
254
+ options = calibdf
255
+
256
+ # select the one closest in time
257
+ result_index = np.abs(options["MJD"] - frame_dict["MJD"]).argmin()
258
+ calib_filepath = options.iloc[result_index, 0]
259
+
260
+ # load the object from disk and return it
261
+ return dtype(calib_filepath)
262
+
263
+ def scan_dir_for_new_entries(self, filedir, look_in_subfolders=True, to_disk=True):
264
+ """
265
+ Scan a folder and subfolder for calibration files and add them all to the caldb
266
+
267
+ Args:
268
+ filedir (str): path to folder to scan (includes all subfolders by default)
269
+ look_in_subfolders (bool): whether to look in subfolders for files. True by default
270
+ to_disk (bool): True by default, will update DB from disk before adding entry and saving it back to disk
271
+ """
272
+ calib_frames = []
273
+ # walk the directory to find all the calibration files
274
+ for dirpath, subfolders, filenames in os.walk(filedir):
275
+ for filename in filenames:
276
+ # hard coded check only for files that end in .fits
277
+ if filename[-5:] != ".fits":
278
+ continue
279
+
280
+ filepath = os.path.join(dirpath, filename)
281
+ frame = data.autoload(filepath)
282
+
283
+ # check what class it has been loaded as. only save frames that fall into calibration classes
284
+ if frame.__class__ in labels:
285
+ calib_frames.append(frame)
286
+
287
+ # the first iteration looks in the basedir
288
+ # if we don't wnat to look in subdirs now, we should break
289
+ if not look_in_subfolders:
290
+ break
291
+
292
+ # load all these files into the caldb
293
+ for calib_frame in calib_frames:
294
+ self.create_entry(calib_frame, to_disk=to_disk)
295
+
296
+ ### Create set of default calibrations
297
+ # Add default detector_params calibration file if it doesn't exist
298
+ if not os.path.exists(os.path.join(corgidrp.default_cal_dir, "DetectorParams_2023-11-01T00:00:00.000.fits")):
299
+ default_detparams = data.DetectorParams({}, date_valid=time.Time("2023-11-01 00:00:00", scale='utc'))
300
+ default_detparams.save(filedir=corgidrp.default_cal_dir)
301
+
302
+ # add default caldb entries
303
+ default_caldb = CalDB()
304
+ default_caldb.scan_dir_for_new_entries(corgidrp.default_cal_dir)