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 +63 -0
- corgidrp/caldb.py +304 -0
- corgidrp/data.py +1013 -0
- corgidrp/detector.py +425 -0
- corgidrp/l1_to_l2a.py +289 -0
- corgidrp/l2a_to_l2b.py +317 -0
- corgidrp/l2b_to_l3.py +31 -0
- corgidrp/l3_to_l4.py +45 -0
- corgidrp/mocks.py +358 -0
- corgidrp/walker.py +168 -0
- corgidrp-0.1.dist-info/LICENSE +27 -0
- corgidrp-0.1.dist-info/METADATA +18 -0
- corgidrp-0.1.dist-info/RECORD +15 -0
- corgidrp-0.1.dist-info/WHEEL +5 -0
- corgidrp-0.1.dist-info/top_level.txt +1 -0
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)
|