daxa 0.0.2__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.
daxa/__init__.py ADDED
@@ -0,0 +1,5 @@
1
+ # This code is a part of the Democratising Archival X-ray Astronomy (DAXA) module.
2
+ # Last modified by David J Turner (turne540@msu.edu) 15/04/2024, 14:49. Copyright (c) The Contributors
3
+
4
+ from .config import daxa_conf, OUTPUT, NUM_CORES, sb_rate
5
+ from .mission.xmm import *
daxa/config.py ADDED
@@ -0,0 +1,88 @@
1
+ # This code is a part of the Democratising Archival X-ray Astronomy (DAXA) module.
2
+ # Last modified by David J Turner (turne540@msu.edu) 15/04/2024, 14:49. Copyright (c) The Contributors
3
+
4
+ import os
5
+ from configparser import ConfigParser
6
+ from warnings import warn
7
+
8
+ import pandas as pd
9
+ import pkg_resources
10
+ from astropy.units import def_unit, ct, deg, s
11
+ from numpy import floor
12
+
13
+ from .exceptions import DAXAConfigError
14
+
15
+ # If XDG_CONFIG_HOME is set, then use that, otherwise use this default config path
16
+ CONFIG_PATH = os.environ.get('XDG_CONFIG_HOME', os.path.join(os.path.expanduser('~'), '.config', 'daxa'))
17
+ # DAXA config file path
18
+ CONFIG_FILE = os.path.join(CONFIG_PATH, 'daxa.cfg')
19
+ # Section of the config file for setting up the DAXA module
20
+ DAXA_CONFIG = {"daxa_save_path": "daxa_output/",
21
+ "num_cores": -1}
22
+
23
+ if not os.path.exists(CONFIG_PATH):
24
+ os.makedirs(CONFIG_PATH)
25
+
26
+ # If first DAXA run, creates default config file
27
+ if not os.path.exists(CONFIG_FILE):
28
+ daxa_default = ConfigParser()
29
+ daxa_default.add_section("DAXA_SETUP")
30
+ daxa_default["DAXA_SETUP"] = DAXA_CONFIG
31
+ with open(CONFIG_FILE, 'w') as new_cfg:
32
+ daxa_default.write(new_cfg)
33
+
34
+ # First time run triggers this message
35
+ warn("A configuration file has been created ({}); you can use it to control where DAXA "
36
+ "stores data by default.".format(CONFIG_FILE), stacklevel=2)
37
+
38
+ daxa_conf = ConfigParser()
39
+ # It would be nice to do configparser interpolation, but it wouldn't handle the lists of energy values
40
+ daxa_conf.read(CONFIG_FILE)
41
+
42
+ try:
43
+ cfg_cores = daxa_conf['DAXA_SETUP'].getint('num_cores')
44
+ except ValueError:
45
+ raise DAXAConfigError("The 'num_cores' configuration parameter must be an integer, with -1 corresponding "
46
+ "to a null value and meaning that DAXA will determine the number of cores to use itself.")
47
+
48
+ # As it turns out, the ConfigParser class is a pain to work with, so we're converting to a dict here
49
+ # Addressing works just the same
50
+ daxa_conf = {str(sect): dict(daxa_conf[str(sect)]) for sect in daxa_conf}
51
+
52
+ if cfg_cores != -1 and cfg_cores <= os.cpu_count():
53
+ # If the user has set a number of cores in the config file then we'll use that.
54
+ NUM_CORES = int(daxa_conf["DAXA_SETUP"]["num_cores"])
55
+ elif cfg_cores != -1:
56
+ raise DAXAConfigError("You have set a num_cores values that is greater than the number of cores available in"
57
+ " the current system ({}).".format(os.cpu_count()))
58
+ else:
59
+ # Going to allow multi-core processing to use 90% of available cores by default, but
60
+ # this can be over-ridden in individual SAS calls.
61
+ NUM_CORES = max(int(floor(os.cpu_count() * 0.9)), 1) # Makes sure that at least one core is used
62
+
63
+
64
+ # This is the default output directory for archives setup by DAXA, though it can be overridden on
65
+ # a mission level
66
+ OUTPUT = os.path.abspath(daxa_conf["DAXA_SETUP"]["daxa_save_path"]) + "/"
67
+
68
+ # Here we read in files that list the errors and warnings in SAS
69
+ errors = pd.read_csv(pkg_resources.resource_filename(__name__, "files/sas_errors.csv"), header="infer")
70
+ warnings = pd.read_csv(pkg_resources.resource_filename(__name__, "files/sas_warnings.csv"), header="infer")
71
+ # Just the names of the errors in two handy constants
72
+ SASERROR_LIST = errors["ErrName"].values
73
+ SASWARNING_LIST = warnings["WarnName"].values
74
+
75
+ # Reading in the file with information on the eROSITA observations that were made available in the
76
+ # eROSITA CalPV release
77
+ EROSITA_CALPV_INFO = pd.read_csv(pkg_resources.resource_filename(__name__, "files/erosita_calpv_info.csv"),
78
+ header="infer", dtype={'ObsID': str})
79
+ # TODO This may end up changing when we get access to the DR1 release - it could be in a format that makes this
80
+ # a bad way of doing it
81
+ # Then doing the same thing, but for the German eRASS:1 release
82
+ ERASS_DE_DR1_INFO = pd.read_csv(pkg_resources.resource_filename(__name__, "files/erass_de_dr1_info.csv"),
83
+ header="infer", dtype={'ObsID': str, 'FIELD1': str, 'FIELD2': str, 'FIELD3': str,
84
+ 'FIELD4': str, 'FIELD5': str, 'FIELD6': str, 'FIELD7': str,
85
+ 'FIELD8': str, 'FIELD9': str})
86
+
87
+ # We define a surface brightness rate astropy unit for use in flaregti to measure thresholds in
88
+ sb_rate = def_unit('sb_rate', ct / (deg**2 * s))
daxa/exceptions.py ADDED
@@ -0,0 +1,469 @@
1
+ # This code is a part of the Democratising Archival X-ray Astronomy (DAXA) module.
2
+ # Last modified by David J Turner (turne540@msu.edu) 16/04/2024, 19:47. Copyright (c) The Contributors
3
+
4
+
5
+ class DAXAConfigError(Exception):
6
+ def __init__(self, *args):
7
+ """
8
+ Exception raised for flawed DAXA config files.
9
+
10
+ :param expression:
11
+ :param message:
12
+ """
13
+ if args:
14
+ self.message = args[0]
15
+ else:
16
+ self.message = None
17
+
18
+ def __str__(self):
19
+ if self.message:
20
+ return '{0} '.format(self.message)
21
+ else:
22
+ return 'DAXAConfig has been raised'
23
+
24
+
25
+ class DAXADownloadError(Exception):
26
+ def __init__(self, *args):
27
+ """
28
+ Exception raised for problems with data downloads orchestrated by DAXA.
29
+
30
+ :param expression:
31
+ :param message:
32
+ """
33
+ if args:
34
+ self.message = args[0]
35
+ else:
36
+ self.message = None
37
+
38
+ def __str__(self):
39
+ if self.message:
40
+ return '{0} '.format(self.message)
41
+ else:
42
+ return 'DAXADownloadError has been raised'
43
+
44
+
45
+ class DAXANotDownloadedError(Exception):
46
+ def __init__(self, *args):
47
+ """
48
+ Exception raised for when something attempts to perform an action that requires data to be downlaoded, and
49
+ it hasn't been.
50
+
51
+ :param expression:
52
+ :param message:
53
+ """
54
+ if args:
55
+ self.message = args[0]
56
+ else:
57
+ self.message = None
58
+
59
+ def __str__(self):
60
+ if self.message:
61
+ return '{0} '.format(self.message)
62
+ else:
63
+ return 'DAXANotDownloadedError has been raised'
64
+
65
+
66
+ class DuplicateMissionError(Exception):
67
+ def __init__(self, *args):
68
+ """
69
+ Exception raised when multiple instances of the same mission are passed to an Archive definition.
70
+
71
+ :param expression:
72
+ :param message:
73
+ """
74
+ if args:
75
+ self.message = args[0]
76
+ else:
77
+ self.message = None
78
+
79
+ def __str__(self):
80
+ if self.message:
81
+ return '{0} '.format(self.message)
82
+ else:
83
+ return 'DuplicateMissionError has been raised'
84
+
85
+
86
+ class ArchiveExistsError(Exception):
87
+ def __init__(self, *args):
88
+ """
89
+ Exception raised when an archive name that has already been used in a particular
90
+ DAXA output directory is used again.
91
+
92
+ :param expression:
93
+ :param message:
94
+ """
95
+ if args:
96
+ self.message = args[0]
97
+ else:
98
+ self.message = None
99
+
100
+ def __str__(self):
101
+ if self.message:
102
+ return '{0} '.format(self.message)
103
+ else:
104
+ return 'ArchiveExistsError has been raised'
105
+
106
+
107
+ class MissionLockedError(Exception):
108
+ def __init__(self, *args):
109
+ """
110
+ Exception raised when a mission instance has been locked (no further changes to selected observations
111
+ can be made) and a change of some kind is attempted.
112
+
113
+ :param expression:
114
+ :param message:
115
+ """
116
+ if args:
117
+ self.message = args[0]
118
+ else:
119
+ self.message = None
120
+
121
+ def __str__(self):
122
+ if self.message:
123
+ return '{0} '.format(self.message)
124
+ else:
125
+ return 'MissionLockedError has been raised'
126
+
127
+
128
+ class SASNotFoundError(Exception):
129
+ def __init__(self, *args):
130
+ """
131
+ Exception raised if the XMM Scientific Analysis System can not be found on the system.
132
+
133
+ :param expression:
134
+ :param message:
135
+ """
136
+ if args:
137
+ self.message = args[0]
138
+ else:
139
+ self.message = None
140
+
141
+ def __str__(self):
142
+ if self.message:
143
+ return '{0} '.format(self.message)
144
+ else:
145
+ return 'SASNotFoundError has been raised'
146
+
147
+
148
+ class SASVersionError(Exception):
149
+ def __init__(self, *args):
150
+ """
151
+ Exception raised if the XMM Scientific Analysis System located on the system is a version
152
+ that is not compatible with DAXA.
153
+
154
+ :param expression:
155
+ :param message:
156
+ """
157
+ if args:
158
+ self.message = args[0]
159
+ else:
160
+ self.message = None
161
+
162
+ def __str__(self):
163
+ if self.message:
164
+ return '{0} '.format(self.message)
165
+ else:
166
+ return 'SASVersionError has been raised'
167
+
168
+ class eSASSNotFoundError(Exception):
169
+ def __init__(self, *args):
170
+ """
171
+ Exception raised if the eROSITA Science Analysis Software System can not be found on the system.
172
+
173
+ :param expression:
174
+ :param message:
175
+ """
176
+ if args:
177
+ self.message = args[0]
178
+ else:
179
+ self.message = None
180
+
181
+ def __str__(self):
182
+ if self.message:
183
+ return '{0} '.format(self.message)
184
+ else:
185
+ return 'eSASSNotFoundError has been raised'
186
+
187
+
188
+ class BackendSoftwareError(Exception):
189
+ def __init__(self, *args):
190
+ """
191
+ Exception raised if a required piece of backend software has not been located.
192
+
193
+ :param expression:
194
+ :param message:
195
+ """
196
+ if args:
197
+ self.message = args[0]
198
+ else:
199
+ self.message = None
200
+
201
+ def __str__(self):
202
+ if self.message:
203
+ return '{0} '.format(self.message)
204
+ else:
205
+ return 'BackendSoftwareError has been raised'
206
+
207
+
208
+ class NoXMMMissionsError(Exception):
209
+ def __init__(self, *args):
210
+ """
211
+ Exception raised if an archive containing no XMM missions is passed to an XMM specific processing function.
212
+
213
+ :param expression:
214
+ :param message:
215
+ """
216
+ if args:
217
+ self.message = args[0]
218
+ else:
219
+ self.message = None
220
+
221
+ def __str__(self):
222
+ if self.message:
223
+ return '{0} '.format(self.message)
224
+ else:
225
+ return 'NoXMMMissionsError has been raised'
226
+
227
+ class NoEROSITAMissionsError(Exception):
228
+ def __init__(self, *args):
229
+ """
230
+ Exception raised if an archive containing no eROSITA missions is passed to an eROSITA specific processing function.
231
+
232
+ :param expression:
233
+ :param message:
234
+ """
235
+ if args:
236
+ self.message = args[0]
237
+ else:
238
+ self.message = None
239
+
240
+ def __str__(self):
241
+ if self.message:
242
+ return '{0} '.format(self.message)
243
+ else:
244
+ return 'NoEROSITAMissionsError has been raised'
245
+
246
+
247
+ class NoProcessingError(Exception):
248
+ def __init__(self, *args):
249
+ """
250
+ Exception raised if a method tries to access processed data when no processing has been applied.
251
+
252
+ :param expression:
253
+ :param message:
254
+ """
255
+ if args:
256
+ self.message = args[0]
257
+ else:
258
+ self.message = None
259
+
260
+ def __str__(self):
261
+ if self.message:
262
+ return '{0} '.format(self.message)
263
+ else:
264
+ return 'NoProcessingError has been raised'
265
+
266
+
267
+ class NoDependencyProcessError(Exception):
268
+ def __init__(self, *args):
269
+ """
270
+ Exception raised if a processing method that the current process depends on has not been run.
271
+
272
+ :param expression:
273
+ :param message:
274
+ """
275
+ if args:
276
+ self.message = args[0]
277
+ else:
278
+ self.message = None
279
+
280
+ def __str__(self):
281
+ if self.message:
282
+ return '{0} '.format(self.message)
283
+ else:
284
+ return 'NoDependencyProcessError has been raised'
285
+
286
+
287
+ class NoObsAfterFilterError(Exception):
288
+ def __init__(self, *args):
289
+ """
290
+ Exception raised if a there are no valid observations left in a mission after filtering processes have been
291
+ applied.
292
+
293
+ :param expression:
294
+ :param message:
295
+ """
296
+ if args:
297
+ self.message = args[0]
298
+ else:
299
+ self.message = None
300
+
301
+ def __str__(self):
302
+ if self.message:
303
+ return '{0} '.format(self.message)
304
+ else:
305
+ return 'NoObsAfterFilterError has been raised'
306
+
307
+
308
+ class IllegalSourceType(Exception):
309
+ def __init__(self, *args):
310
+ """
311
+ Exception raised if a source type that isn't in the DAXA source type taxonomy has been used.
312
+
313
+ :param expression:
314
+ :param message:
315
+ """
316
+ if args:
317
+ self.message = args[0]
318
+ else:
319
+ self.message = None
320
+
321
+ def __str__(self):
322
+ if self.message:
323
+ return '{0} '.format(self.message)
324
+ else:
325
+ return 'IllegalSourceType has been raised'
326
+
327
+
328
+ class NoTargetSourceTypeInfo(Exception):
329
+ def __init__(self, *args):
330
+ """
331
+ Exception raised if a mission doesn't have any information on each observation's target source type.
332
+
333
+ :param expression:
334
+ :param message:
335
+ """
336
+ if args:
337
+ self.message = args[0]
338
+ else:
339
+ self.message = None
340
+
341
+ def __str__(self):
342
+ if self.message:
343
+ return '{0} '.format(self.message)
344
+ else:
345
+ return 'NoSourceTypeInfo has been raised'
346
+
347
+
348
+ class ObsNotAssociatedError(Exception):
349
+ def __init__(self, *args):
350
+ """
351
+ Exception raised if an observation is not associated with a particular mission's filtered dataset.
352
+
353
+ :param expression:
354
+ :param message:
355
+ """
356
+ if args:
357
+ self.message = args[0]
358
+ else:
359
+ self.message = None
360
+
361
+ def __str__(self):
362
+ if self.message:
363
+ return '{0} '.format(self.message)
364
+ else:
365
+ return 'ObsNotAssociatedError has been raised'
366
+
367
+
368
+ class MissionNotAssociatedError(Exception):
369
+ def __init__(self, *args):
370
+ """
371
+ Exception raised if a mission is not associated with a particular archive dataset.
372
+
373
+ :param expression:
374
+ :param message:
375
+ """
376
+ if args:
377
+ self.message = args[0]
378
+ else:
379
+ self.message = None
380
+
381
+ def __str__(self):
382
+ if self.message:
383
+ return '{0} '.format(self.message)
384
+ else:
385
+ return 'MissionNotAssociatedError has been raised'
386
+
387
+
388
+ class NoRegionsAssociatedError(Exception):
389
+ def __init__(self, *args):
390
+ """
391
+ Exception raised if there are no source regions available for the user to retrieve.
392
+
393
+ :param expression:
394
+ :param message:
395
+ """
396
+ if args:
397
+ self.message = args[0]
398
+ else:
399
+ self.message = None
400
+
401
+ def __str__(self):
402
+ if self.message:
403
+ return '{0} '.format(self.message)
404
+ else:
405
+ return 'NoRegionsAssociatedError has been raised'
406
+
407
+
408
+ class IncompatibleSaveError(Exception):
409
+ def __init__(self, *args):
410
+ """
411
+ Exception raised if a save file being read in to reinstate a DAXA mission or archive is being used incorrectly
412
+ and is not compatible with the process.
413
+
414
+ :param expression:
415
+ :param message:
416
+ """
417
+ if args:
418
+ self.message = args[0]
419
+ else:
420
+ self.message = None
421
+
422
+ def __str__(self):
423
+ if self.message:
424
+ return '{0} '.format(self.message)
425
+ else:
426
+ return 'IncompatibleSaveError has been raised'
427
+
428
+
429
+ class PreProcessedNotSupportedError(Exception):
430
+ def __init__(self, *args):
431
+ """
432
+ Exception raised if the user attempts to access pre-processed data for a mission class that does not support
433
+ it (usually because the data are not available in the archive).
434
+
435
+ :param expression:
436
+ :param message:
437
+ """
438
+ if args:
439
+ self.message = args[0]
440
+ else:
441
+ self.message = None
442
+
443
+ def __str__(self):
444
+ if self.message:
445
+ return '{0} '.format(self.message)
446
+ else:
447
+ return 'PreProcessedNotSupportedError has been raised'
448
+
449
+
450
+ class PreProcessedNotAvailableError(Exception):
451
+ def __init__(self, *args):
452
+ """
453
+ Exception raised if the user attempts to access a pre-processed product that is not available for the
454
+ mission - e.g. if they try to access an image for an energy band not present in the archive.
455
+
456
+ :param expression:
457
+ :param message:
458
+ """
459
+ if args:
460
+ self.message = args[0]
461
+ else:
462
+ self.message = None
463
+
464
+ def __str__(self):
465
+ if self.message:
466
+ return '{0} '.format(self.message)
467
+ else:
468
+ return 'PreProcessedNotAvailableError has been raised'
469
+