sedlib 1.0.0__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.
- sedlib/__init__.py +47 -0
- sedlib/bol2rad.py +552 -0
- sedlib/catalog.py +1141 -0
- sedlib/core.py +6059 -0
- sedlib/data/__init__.py +2 -0
- sedlib/data/temp_to_bc_coefficients.yaml +62 -0
- sedlib/filter/__init__.py +5 -0
- sedlib/filter/core.py +1064 -0
- sedlib/filter/data/__init__.py +2 -0
- sedlib/filter/data/svo_all_filter_database.pickle +0 -0
- sedlib/filter/data/svo_filter_catalog.pickle +0 -0
- sedlib/filter/data/svo_meta_data.xml +1282 -0
- sedlib/filter/utils.py +71 -0
- sedlib/helper.py +361 -0
- sedlib/utils.py +789 -0
- sedlib/version.py +12 -0
- sedlib-1.0.0.dist-info/METADATA +611 -0
- sedlib-1.0.0.dist-info/RECORD +21 -0
- sedlib-1.0.0.dist-info/WHEEL +5 -0
- sedlib-1.0.0.dist-info/licenses/LICENSE +201 -0
- sedlib-1.0.0.dist-info/top_level.txt +1 -0
sedlib/filter/core.py
ADDED
@@ -0,0 +1,1064 @@
|
|
1
|
+
#!/usr/bin/env python
|
2
|
+
|
3
|
+
"""Core class for the filter module."""
|
4
|
+
|
5
|
+
__all__ = ["Filter"]
|
6
|
+
|
7
|
+
import os
|
8
|
+
import logging
|
9
|
+
import warnings
|
10
|
+
from io import BytesIO
|
11
|
+
from fnmatch import fnmatch
|
12
|
+
import requests
|
13
|
+
from pathlib import Path
|
14
|
+
from importlib import resources
|
15
|
+
|
16
|
+
from bs4 import BeautifulSoup
|
17
|
+
from typing import Optional, Union, List, Any
|
18
|
+
|
19
|
+
import numpy as np
|
20
|
+
import pandas as pd
|
21
|
+
import matplotlib.pyplot as plt
|
22
|
+
|
23
|
+
from astropy import units as u
|
24
|
+
from astropy.io import votable
|
25
|
+
from astropy.modeling.tabular import Tabular1D
|
26
|
+
from astropy.utils.exceptions import AstropyWarning
|
27
|
+
from astropy.utils.data import download_file
|
28
|
+
|
29
|
+
from .utils import SVO_FILTER_URL, InMemoryHandler
|
30
|
+
|
31
|
+
|
32
|
+
# Set up logging
|
33
|
+
DEFAULT_LOG_FORMAT = '%(asctime)s - %(levelname)s - %(message)s'
|
34
|
+
DEFAULT_LOG_DATEFMT = '%Y-%m-%dT%H:%M:%S'
|
35
|
+
DEFAULT_LOG_LEVEL = logging.DEBUG
|
36
|
+
|
37
|
+
def setup_logger(
|
38
|
+
name: str,
|
39
|
+
log_file: Optional[Union[str, Path]] = 'sed.log',
|
40
|
+
log_level: int = DEFAULT_LOG_LEVEL,
|
41
|
+
log_format: str = DEFAULT_LOG_FORMAT,
|
42
|
+
log_datefmt: str = DEFAULT_LOG_DATEFMT,
|
43
|
+
use_file_handler: bool = False,
|
44
|
+
use_memory_handler: bool = True,
|
45
|
+
memory_capacity: Optional[int] = None
|
46
|
+
) -> tuple[logging.Logger, Optional[InMemoryHandler]]:
|
47
|
+
"""Set up a logger with optional file and memory handlers.
|
48
|
+
|
49
|
+
Parameters
|
50
|
+
----------
|
51
|
+
name : str
|
52
|
+
Logger name
|
53
|
+
log_file : Optional[Union[str, Path]]
|
54
|
+
Path to log file. If None, file logging is disabled
|
55
|
+
log_level : int
|
56
|
+
Logging level
|
57
|
+
log_format : str
|
58
|
+
Log message format
|
59
|
+
log_datefmt : str
|
60
|
+
Date format for log messages
|
61
|
+
use_file_handler : bool
|
62
|
+
Whether to enable file logging
|
63
|
+
use_memory_handler : bool
|
64
|
+
Whether to enable in-memory logging
|
65
|
+
memory_capacity : Optional[int]
|
66
|
+
Maximum number of log records to store in memory (if enabled)
|
67
|
+
If None, no limit is applied
|
68
|
+
|
69
|
+
Returns
|
70
|
+
-------
|
71
|
+
tuple[logging.Logger, Optional[InMemoryHandler]]
|
72
|
+
Configured logger and memory handler (if enabled)
|
73
|
+
"""
|
74
|
+
logger = logging.getLogger(name)
|
75
|
+
logger.setLevel(log_level)
|
76
|
+
|
77
|
+
# Remove any existing handlers
|
78
|
+
for handler in logger.handlers[:]:
|
79
|
+
logger.removeHandler(handler)
|
80
|
+
|
81
|
+
# Create formatter
|
82
|
+
formatter = logging.Formatter(log_format, datefmt=log_datefmt)
|
83
|
+
|
84
|
+
# Set up file handler if requested
|
85
|
+
if use_file_handler and log_file:
|
86
|
+
fh = logging.FileHandler(log_file)
|
87
|
+
fh.setLevel(log_level)
|
88
|
+
fh.setFormatter(formatter)
|
89
|
+
logger.addHandler(fh)
|
90
|
+
|
91
|
+
# Set up memory handler if requested
|
92
|
+
memory_handler = None
|
93
|
+
if use_memory_handler:
|
94
|
+
memory_handler = InMemoryHandler(capacity=memory_capacity)
|
95
|
+
memory_handler.setLevel(log_level)
|
96
|
+
memory_handler.setFormatter(formatter)
|
97
|
+
logger.addHandler(memory_handler)
|
98
|
+
|
99
|
+
return logger, memory_handler
|
100
|
+
|
101
|
+
# Set up default logger with both file and memory handlers
|
102
|
+
logger, in_memory_handler = setup_logger(__name__)
|
103
|
+
|
104
|
+
warnings.simplefilter('ignore', category=AstropyWarning)
|
105
|
+
|
106
|
+
|
107
|
+
class Filter:
|
108
|
+
"""
|
109
|
+
A class for managing astronomical filter transmission curves.
|
110
|
+
|
111
|
+
This class provides access to filter transmission curves from the SVO Filter
|
112
|
+
Profile Service, which contains thousands of astronomical filters used by
|
113
|
+
various surveys and instruments. Filters can be loaded from the SVO service
|
114
|
+
or created from custom data.
|
115
|
+
|
116
|
+
Parameters
|
117
|
+
----------
|
118
|
+
name : str, optional
|
119
|
+
Filter identifier in SVO format (e.g., 'Generic/Johnson.V').
|
120
|
+
The filter name must match the naming convention used by the SVO service.
|
121
|
+
method : str, default 'linear'
|
122
|
+
Interpolation method for filter transmission curves.
|
123
|
+
Available methods: 'linear', 'nearest'.
|
124
|
+
bounds_error : bool, default False
|
125
|
+
If True, raise ValueError when interpolated values are requested outside
|
126
|
+
the domain of the input data. If False, use fill_value.
|
127
|
+
fill_value : float or None, default 0.0
|
128
|
+
Value to use for points outside the interpolation domain.
|
129
|
+
If None, values outside the domain are extrapolated.
|
130
|
+
cache : bool, default True
|
131
|
+
If True, cache filter data for faster subsequent access.
|
132
|
+
timeout : int, default 10
|
133
|
+
Timeout in seconds for HTTP requests to SVO service.
|
134
|
+
log_to_file : bool, default False
|
135
|
+
Whether to enable file logging.
|
136
|
+
log_to_memory : bool, default True
|
137
|
+
Whether to enable in-memory logging.
|
138
|
+
log_level : int, default logging.DEBUG
|
139
|
+
Logging level to use.
|
140
|
+
log_file : str or Path, optional
|
141
|
+
Path to log file. Default is 'sed.log'.
|
142
|
+
|
143
|
+
Attributes
|
144
|
+
----------
|
145
|
+
name : str
|
146
|
+
Filter identifier.
|
147
|
+
wavelength : astropy.units.Quantity
|
148
|
+
Wavelength array for the filter transmission curve.
|
149
|
+
transmission : numpy.ndarray
|
150
|
+
Transmission values corresponding to wavelengths.
|
151
|
+
data : astropy.modeling.tabular.Tabular1D
|
152
|
+
Interpolated transmission function.
|
153
|
+
_spec : dict
|
154
|
+
Filter specifications from SVO service.
|
155
|
+
|
156
|
+
Methods
|
157
|
+
-------
|
158
|
+
from_svo(name)
|
159
|
+
Load filter transmission curve from SVO service.
|
160
|
+
from_data(name, wavelength, transmission)
|
161
|
+
Create filter from custom wavelength and transmission data.
|
162
|
+
search(name, case=False)
|
163
|
+
Search for filter names in SVO catalog using wildcards.
|
164
|
+
apply(wavelength, flux, error=None, plot=False)
|
165
|
+
Apply filter to spectrum and return filtered flux.
|
166
|
+
plot(ax=None, figsize=(10, 6), title=None, xlabel=None, ylabel=None, filename=None)
|
167
|
+
Plot filter transmission curve.
|
168
|
+
get_logs(log_type='all')
|
169
|
+
Get stored log records (in-memory logging only).
|
170
|
+
dump_logs(filename)
|
171
|
+
Dump log records to file (in-memory logging only).
|
172
|
+
clear_logs()
|
173
|
+
Clear stored log records (in-memory logging only).
|
174
|
+
|
175
|
+
Raises
|
176
|
+
------
|
177
|
+
ValueError
|
178
|
+
If filter name format is invalid or filter not found.
|
179
|
+
requests.RequestException
|
180
|
+
If network request to SVO service fails.
|
181
|
+
TypeError
|
182
|
+
If input parameters have incorrect types.
|
183
|
+
|
184
|
+
Examples
|
185
|
+
--------
|
186
|
+
>>> from sedlib import Filter
|
187
|
+
>>> from astropy import units as u
|
188
|
+
>>>
|
189
|
+
>>> # Load Johnson V filter
|
190
|
+
>>> f = Filter('Generic/Johnson.V')
|
191
|
+
>>> transmission = f(5500 * u.AA)
|
192
|
+
>>> print(f"Transmission at 5500 Å: {transmission:.3f}")
|
193
|
+
>>>
|
194
|
+
>>> # Search for TESS filters
|
195
|
+
>>> f_search = Filter()
|
196
|
+
>>> tess_filters = f_search.search('*TESS*')
|
197
|
+
>>> print(f"Found TESS filters: {tess_filters}")
|
198
|
+
>>>
|
199
|
+
>>> # Create custom filter
|
200
|
+
>>> import numpy as np
|
201
|
+
>>> wl = np.linspace(4000, 8000, 100) * u.AA
|
202
|
+
>>> trans = np.exp(-((wl - 5500*u.AA) / (500*u.AA))**2)
|
203
|
+
>>> custom_filter = Filter()
|
204
|
+
>>> custom_filter.from_data('Custom/V', wl, trans)
|
205
|
+
>>> custom_filter.plot()
|
206
|
+
"""
|
207
|
+
|
208
|
+
def __init__(
|
209
|
+
self,
|
210
|
+
name: Optional[str] = None,
|
211
|
+
method: str = 'linear',
|
212
|
+
bounds_error: bool = False,
|
213
|
+
fill_value: Union[float, None] = 0.,
|
214
|
+
cache: bool = True,
|
215
|
+
timeout: int = 10,
|
216
|
+
log_to_file: bool = False,
|
217
|
+
log_to_memory: bool = True,
|
218
|
+
log_level: int = DEFAULT_LOG_LEVEL,
|
219
|
+
log_file: Optional[Union[str, Path]] = 'sed.log'
|
220
|
+
) -> None:
|
221
|
+
# Configure instance-specific logging if different from default
|
222
|
+
if (log_level != DEFAULT_LOG_LEVEL or
|
223
|
+
not log_to_file or
|
224
|
+
not log_to_memory or
|
225
|
+
log_file != 'sed.log'):
|
226
|
+
self.logger, self.memory_handler = setup_logger(
|
227
|
+
f"{__name__}.{id(self)}",
|
228
|
+
log_file=log_file,
|
229
|
+
use_file_handler=log_to_file,
|
230
|
+
use_memory_handler=log_to_memory,
|
231
|
+
log_level=log_level
|
232
|
+
)
|
233
|
+
else:
|
234
|
+
self.logger = logger
|
235
|
+
self.memory_handler = in_memory_handler
|
236
|
+
|
237
|
+
self.logger.info(
|
238
|
+
f"BEGIN __init__ - Creating new Filter instance with name='{name}', "
|
239
|
+
f"method='{method}'"
|
240
|
+
)
|
241
|
+
|
242
|
+
param_types = {
|
243
|
+
'name': (type(None), str),
|
244
|
+
'method': str,
|
245
|
+
'cache': bool,
|
246
|
+
'bounds_error': bool,
|
247
|
+
'fill_value': (float, type(None)),
|
248
|
+
}
|
249
|
+
|
250
|
+
for param, expected_types in param_types.items():
|
251
|
+
if not isinstance(locals()[param], expected_types):
|
252
|
+
type_names = ' or '.join([
|
253
|
+
t.__name__ for t in (
|
254
|
+
expected_types if isinstance(expected_types, tuple)
|
255
|
+
else (expected_types,)
|
256
|
+
)
|
257
|
+
])
|
258
|
+
self.logger.error(
|
259
|
+
f"Type validation failed for parameter '{param}' - "
|
260
|
+
f"expected {type_names}"
|
261
|
+
)
|
262
|
+
raise TypeError(f'`{param}` must be {type_names} type.')
|
263
|
+
|
264
|
+
if method not in ['linear', 'nearest']:
|
265
|
+
self.logger.error(
|
266
|
+
f"Invalid method '{method}' specified - "
|
267
|
+
"must be 'linear' or 'nearest'"
|
268
|
+
)
|
269
|
+
raise ValueError('`method` must be one of "linear" or "nearest"!')
|
270
|
+
|
271
|
+
self.name = name
|
272
|
+
self._method = method
|
273
|
+
self._cache = cache
|
274
|
+
self._timeout = timeout
|
275
|
+
self._bounds_error = bounds_error
|
276
|
+
self._fill_value = fill_value
|
277
|
+
|
278
|
+
self.wavelength = None
|
279
|
+
self.transmission = None
|
280
|
+
self.data = None
|
281
|
+
self._spec = None
|
282
|
+
|
283
|
+
self._xml = None
|
284
|
+
self._meta_xml = None
|
285
|
+
|
286
|
+
self._default_flux_unit = u.erg / (u.s * u.cm**2 * u.Hz)
|
287
|
+
|
288
|
+
self._catalog = None
|
289
|
+
|
290
|
+
if name is not None:
|
291
|
+
self.logger.debug(
|
292
|
+
f"Initializing filter data from SVO for name='{name}'"
|
293
|
+
)
|
294
|
+
self.from_svo()
|
295
|
+
|
296
|
+
self.logger.info("END __init__ - Filter instance created successfully")
|
297
|
+
|
298
|
+
def __call__(self, wavelength: u.Quantity) -> Optional[float]:
|
299
|
+
self.logger.debug(
|
300
|
+
f"BEGIN __call__ - Evaluating filter at wavelength={wavelength}"
|
301
|
+
)
|
302
|
+
|
303
|
+
if wavelength.unit != u.AA:
|
304
|
+
self.logger.debug("Converting wavelength unit to Angstrom")
|
305
|
+
wavelength = wavelength.to(u.AA, equivalencies=u.spectral())
|
306
|
+
|
307
|
+
if self.data is not None:
|
308
|
+
result = self.data(wavelength)
|
309
|
+
self.logger.debug(
|
310
|
+
f"END __call__ - Returning transmission value: {result}"
|
311
|
+
)
|
312
|
+
return result
|
313
|
+
|
314
|
+
self.logger.warning(
|
315
|
+
"END __call__ - No filter data available, returning None"
|
316
|
+
)
|
317
|
+
return None
|
318
|
+
|
319
|
+
def __repr__(self) -> str:
|
320
|
+
if self._spec is not None:
|
321
|
+
return self._spec['Description']
|
322
|
+
|
323
|
+
return self.name
|
324
|
+
|
325
|
+
def __str__(self) -> str:
|
326
|
+
if self._spec is not None:
|
327
|
+
return self._spec['Description']
|
328
|
+
|
329
|
+
return self.name
|
330
|
+
|
331
|
+
def __getitem__(self, item: str) -> Any:
|
332
|
+
if self._spec is None:
|
333
|
+
return None
|
334
|
+
|
335
|
+
return self._spec[item]
|
336
|
+
|
337
|
+
def __eq__(self, object: Optional['Filter']) -> bool:
|
338
|
+
if object is None:
|
339
|
+
return False
|
340
|
+
|
341
|
+
if not isinstance(object, Filter):
|
342
|
+
raise TypeError('`object` must be `Filter` type.')
|
343
|
+
|
344
|
+
return float(self.WavelengthEff.value) == float(object.WavelengthEff.value)
|
345
|
+
|
346
|
+
def __ne__(self, object: 'Filter') -> bool:
|
347
|
+
if not isinstance(object, Filter):
|
348
|
+
raise TypeError('`object` must be `Filter` type.')
|
349
|
+
|
350
|
+
return float(self.WavelengthEff.value) != float(object.WavelengthEff.value)
|
351
|
+
|
352
|
+
def __gt__(self, object: 'Filter') -> bool:
|
353
|
+
if not isinstance(object, Filter):
|
354
|
+
raise TypeError('`object` must be `Filter` type.')
|
355
|
+
|
356
|
+
return float(self.WavelengthEff.value) > float(object.WavelengthEff.value)
|
357
|
+
|
358
|
+
def __lt__(self, object: 'Filter') -> bool:
|
359
|
+
if not isinstance(object, Filter):
|
360
|
+
raise TypeError('`object` must be `Filter` type.')
|
361
|
+
|
362
|
+
return float(self.WavelengthEff.value) < float(object.WavelengthEff.value)
|
363
|
+
|
364
|
+
def __ge__(self, object: 'Filter') -> bool:
|
365
|
+
if not isinstance(object, Filter):
|
366
|
+
raise TypeError('`object` must be `Filter` type.')
|
367
|
+
|
368
|
+
return float(self.WavelengthEff.value) >= float(object.WavelengthEff.value)
|
369
|
+
|
370
|
+
def __le__(self, object: 'Filter') -> bool:
|
371
|
+
if not isinstance(object, Filter):
|
372
|
+
raise TypeError('`object` must be `Filter` type.')
|
373
|
+
|
374
|
+
return float(self.WavelengthEff.value) <= float(object.WavelengthEff.value)
|
375
|
+
|
376
|
+
def _prepare(self) -> None:
|
377
|
+
# Use importlib.resources to access package data files
|
378
|
+
try:
|
379
|
+
# For Python 3.9+
|
380
|
+
meta_file = resources.files('sedlib.filter.data').joinpath('svo_meta_data.xml')
|
381
|
+
with meta_file.open('r') as f:
|
382
|
+
self._meta_xml = BeautifulSoup(f, features='lxml')
|
383
|
+
|
384
|
+
catalog_file = resources.files('sedlib.filter.data').joinpath('svo_all_filter_database.pickle')
|
385
|
+
with catalog_file.open('rb') as f:
|
386
|
+
self._catalog = pd.read_pickle(f)
|
387
|
+
except AttributeError:
|
388
|
+
# Fallback for Python 3.7-3.8 using older API
|
389
|
+
with resources.open_text('sedlib.filter.data', 'svo_meta_data.xml') as f:
|
390
|
+
self._meta_xml = BeautifulSoup(f, features='lxml')
|
391
|
+
|
392
|
+
with resources.open_binary('sedlib.filter.data', 'svo_all_filter_database.pickle') as f:
|
393
|
+
self._catalog = pd.read_pickle(f)
|
394
|
+
|
395
|
+
def _parse_xml(self) -> None:
|
396
|
+
if self._xml is None:
|
397
|
+
return
|
398
|
+
|
399
|
+
self._spec = dict()
|
400
|
+
params = self._xml.find_all('PARAM')
|
401
|
+
|
402
|
+
for i, param in enumerate(params):
|
403
|
+
attrs = param.attrs
|
404
|
+
|
405
|
+
value = attrs['value']
|
406
|
+
|
407
|
+
if attrs['datatype'] == 'double':
|
408
|
+
value = float(attrs['value']) * u.Unit(attrs['unit'])
|
409
|
+
|
410
|
+
if 'Unit' in attrs['name']:
|
411
|
+
continue
|
412
|
+
|
413
|
+
self._spec[attrs['name']] = value
|
414
|
+
self.__dict__[attrs['name']] = value
|
415
|
+
|
416
|
+
def from_svo(self, name: Optional[str] = None) -> None:
|
417
|
+
"""
|
418
|
+
Gets filter from SVO service
|
419
|
+
|
420
|
+
Parameters
|
421
|
+
----------
|
422
|
+
name : str
|
423
|
+
filter name
|
424
|
+
|
425
|
+
Examples
|
426
|
+
--------
|
427
|
+
>>> from sedlib import Filter
|
428
|
+
>>>
|
429
|
+
>>> f = Filter()
|
430
|
+
>>> f.from_svo('Generic/Johnson.V')
|
431
|
+
>>> f
|
432
|
+
Johnson V
|
433
|
+
"""
|
434
|
+
self.logger.info(
|
435
|
+
f"BEGIN from_svo - Fetching filter data from SVO. "
|
436
|
+
f"name='{name or self.name}'"
|
437
|
+
)
|
438
|
+
|
439
|
+
if not isinstance(name, (type(None), str)):
|
440
|
+
self.logger.error(f"Invalid name type: {type(name)}")
|
441
|
+
raise TypeError('`name` must be `str` or `None` type.')
|
442
|
+
|
443
|
+
if name is not None:
|
444
|
+
self.name = name.strip()
|
445
|
+
|
446
|
+
try:
|
447
|
+
self.logger.debug(
|
448
|
+
f"Downloading filter data from URL: {SVO_FILTER_URL}{self.name}"
|
449
|
+
)
|
450
|
+
path = download_file(
|
451
|
+
f'{SVO_FILTER_URL}{self.name}', cache=self._cache,
|
452
|
+
timeout=self._timeout, allow_insecure=True
|
453
|
+
)
|
454
|
+
self.logger.debug("Filter data download successful")
|
455
|
+
except Exception as e:
|
456
|
+
self.logger.error(f"Failed to download filter data: {str(e)}")
|
457
|
+
raise requests.ConnectionError(
|
458
|
+
f"Failed to download filter data: {str(e)}"
|
459
|
+
)
|
460
|
+
|
461
|
+
with open(path, 'r') as f:
|
462
|
+
text = f.read()
|
463
|
+
|
464
|
+
self._xml = BeautifulSoup(text, features='xml')
|
465
|
+
|
466
|
+
info = self._xml.find('INFO')
|
467
|
+
if info is None or info.attrs.get('value') != 'OK':
|
468
|
+
self.logger.error(f"Filter '{self.name}' not found in SVO database")
|
469
|
+
raise ValueError(f'Filter "{self.name}" is not found!')
|
470
|
+
|
471
|
+
self.logger.debug("Parsing filter metadata from XML")
|
472
|
+
self._parse_xml()
|
473
|
+
|
474
|
+
try:
|
475
|
+
self.logger.debug("Parsing filter transmission data")
|
476
|
+
vt = votable.parse_single_table(BytesIO(text.encode())).to_table()
|
477
|
+
self.wavelength = vt['Wavelength']
|
478
|
+
self.transmission = vt['Transmission']
|
479
|
+
except Exception as e:
|
480
|
+
self.logger.error(
|
481
|
+
f"Failed to parse filter transmission data: {str(e)}"
|
482
|
+
)
|
483
|
+
raise ValueError(f"Failed to parse filter data: {str(e)}")
|
484
|
+
|
485
|
+
self.logger.debug("Creating interpolation function for filter curve")
|
486
|
+
self.data = Tabular1D(
|
487
|
+
points=self.wavelength, lookup_table=self.transmission,
|
488
|
+
method=self._method, bounds_error=self._bounds_error,
|
489
|
+
fill_value=self._fill_value
|
490
|
+
)
|
491
|
+
|
492
|
+
self.logger.info(
|
493
|
+
f"END from_svo - Successfully loaded filter '{self.name}' from SVO"
|
494
|
+
)
|
495
|
+
|
496
|
+
def from_data(
|
497
|
+
self,
|
498
|
+
name: str,
|
499
|
+
wavelength: u.Quantity,
|
500
|
+
transmission: np.ndarray
|
501
|
+
) -> None:
|
502
|
+
"""
|
503
|
+
Creates filter from custom data set
|
504
|
+
|
505
|
+
Parameters
|
506
|
+
----------
|
507
|
+
name : str
|
508
|
+
filter name
|
509
|
+
|
510
|
+
wavelength : astropy.units.quantity.Quantity
|
511
|
+
wavelength array.
|
512
|
+
The unit must be Angstrom [A].
|
513
|
+
|
514
|
+
transmission : np.array
|
515
|
+
transmission array.
|
516
|
+
It must be unitless. Values should be normalized to 1.
|
517
|
+
|
518
|
+
Examples
|
519
|
+
--------
|
520
|
+
>>> import numpy as np
|
521
|
+
>>> from astropy import units as u
|
522
|
+
>>> from sedlib import Filter
|
523
|
+
>>>
|
524
|
+
>>> f = Filter()
|
525
|
+
>>>
|
526
|
+
>>> # Creating a Neutral Density filter (ND 1.0)
|
527
|
+
>>> w = np.arange(3000, 7000, 10) * u.AA
|
528
|
+
>>> t = np.full(len(w), fill_value=0.1)
|
529
|
+
>>>
|
530
|
+
>>> f.from_data(name='ND 1.0', wavelength=w, transmission=t)
|
531
|
+
>>> f
|
532
|
+
ND 1.0
|
533
|
+
"""
|
534
|
+
self.logger.info(
|
535
|
+
f"BEGIN from_data - Creating custom filter '{name}' with "
|
536
|
+
f"{len(wavelength)} points"
|
537
|
+
)
|
538
|
+
|
539
|
+
if not isinstance(name, str):
|
540
|
+
self.logger.error(f"Invalid name type: {type(name)}")
|
541
|
+
raise TypeError('`name` must be `str` type.')
|
542
|
+
|
543
|
+
if not isinstance(wavelength, u.Quantity):
|
544
|
+
self.logger.error(f"Invalid wavelength type: {type(wavelength)}")
|
545
|
+
raise TypeError('`wavelength` must be Quantity object')
|
546
|
+
|
547
|
+
if not isinstance(transmission, np.ndarray):
|
548
|
+
self.logger.error(f"Invalid transmission type: {type(transmission)}")
|
549
|
+
raise TypeError('`transmission` must be numpy array')
|
550
|
+
|
551
|
+
if wavelength.unit != u.AA:
|
552
|
+
self.logger.error(f"Invalid wavelength unit: {wavelength.unit}")
|
553
|
+
raise TypeError('`wavelength` must be Angstrom')
|
554
|
+
|
555
|
+
self.name = name
|
556
|
+
self.wavelength = wavelength
|
557
|
+
self.transmission = transmission
|
558
|
+
|
559
|
+
self.logger.debug("Creating interpolation function for custom filter curve")
|
560
|
+
self.data = Tabular1D(
|
561
|
+
points=self.wavelength,
|
562
|
+
lookup_table=self.transmission,
|
563
|
+
method=self._method,
|
564
|
+
bounds_error=self._bounds_error,
|
565
|
+
fill_value=self._fill_value
|
566
|
+
)
|
567
|
+
|
568
|
+
self.logger.info(
|
569
|
+
f"END from_data - Custom filter '{name}' created successfully"
|
570
|
+
)
|
571
|
+
|
572
|
+
def apply(
|
573
|
+
self,
|
574
|
+
wavelength: u.Quantity,
|
575
|
+
flux: u.Quantity,
|
576
|
+
error: Optional[Union[u.Quantity, np.ndarray]] = None,
|
577
|
+
plot: bool = False
|
578
|
+
) -> np.ndarray:
|
579
|
+
"""
|
580
|
+
Applies the filter to the given spectrum
|
581
|
+
|
582
|
+
Parameters
|
583
|
+
----------
|
584
|
+
wavelength : astropy.units.quantity.Quantity
|
585
|
+
wavelength array.
|
586
|
+
The unit must be Angstrom [A].
|
587
|
+
|
588
|
+
flux : np.array or astropy.units.quantity.Quantity
|
589
|
+
flux array
|
590
|
+
|
591
|
+
error : None, np.array or astropy.units.quantity.Quantity
|
592
|
+
error of flux
|
593
|
+
|
594
|
+
plot : bool
|
595
|
+
plots the spectrum passing through the filter
|
596
|
+
Default value is False
|
597
|
+
|
598
|
+
Return
|
599
|
+
------
|
600
|
+
np.array
|
601
|
+
|
602
|
+
Examples
|
603
|
+
--------
|
604
|
+
>>> import numpy as np
|
605
|
+
>>> from astropy import units as u
|
606
|
+
>>> from sedlib import Filter
|
607
|
+
>>>
|
608
|
+
>>> # generate fake date
|
609
|
+
>>> w = np.arange(3000, 7000, 10) * u.AA
|
610
|
+
>>> f = np.random.random(len(w))
|
611
|
+
>>>
|
612
|
+
>>> f = Filter('Generic/Johnson.V')
|
613
|
+
>>> applied_filter = f.apply(w, f)
|
614
|
+
"""
|
615
|
+
self.logger.info(
|
616
|
+
f"BEGIN apply - Applying filter '{self.name}' to spectrum with "
|
617
|
+
f"{len(wavelength)} points"
|
618
|
+
)
|
619
|
+
|
620
|
+
if not isinstance(wavelength, u.Quantity):
|
621
|
+
self.logger.error(f"Invalid wavelength type: {type(wavelength)}")
|
622
|
+
raise TypeError('`wavelength` must be Quantity object')
|
623
|
+
|
624
|
+
if not isinstance(flux, u.Quantity):
|
625
|
+
self.logger.error(f"Invalid flux type: {type(flux)}")
|
626
|
+
raise TypeError('`flux` must be Quantity object')
|
627
|
+
|
628
|
+
if not isinstance(error, (u.Quantity, np.ndarray, type(None))):
|
629
|
+
self.logger.error(f"Invalid error type: {type(error)}")
|
630
|
+
raise TypeError('`error` must be Quantity or None')
|
631
|
+
|
632
|
+
if not isinstance(plot, bool):
|
633
|
+
self.logger.error(f"Invalid plot type: {type(plot)}")
|
634
|
+
raise TypeError('`plot` must be bool object')
|
635
|
+
|
636
|
+
if wavelength.unit != u.AA:
|
637
|
+
self.logger.error(f"Invalid wavelength unit: {wavelength.unit}")
|
638
|
+
raise TypeError('`wavelength` must be Angstrom')
|
639
|
+
|
640
|
+
self.logger.debug("Computing filter-weighted flux")
|
641
|
+
result = self.data(wavelength) * flux
|
642
|
+
|
643
|
+
self.logger.info(f"END apply - Filter successfully applied to spectrum")
|
644
|
+
return result
|
645
|
+
|
646
|
+
def search(
|
647
|
+
self,
|
648
|
+
name: Optional[str] = None,
|
649
|
+
case: bool = False
|
650
|
+
) -> List[str]:
|
651
|
+
"""
|
652
|
+
Searches filter name from SVO catalog.
|
653
|
+
Wild characters can be used in the filter name.
|
654
|
+
|
655
|
+
Parameters
|
656
|
+
----------
|
657
|
+
name : None or str
|
658
|
+
filter name.
|
659
|
+
If name is None, returns all filter names
|
660
|
+
|
661
|
+
case : bool
|
662
|
+
Searches for filter names in a case-sensitive
|
663
|
+
Default value is False
|
664
|
+
|
665
|
+
Returns
|
666
|
+
-------
|
667
|
+
list of str
|
668
|
+
|
669
|
+
Examples
|
670
|
+
--------
|
671
|
+
>>> from sedlib import Filter
|
672
|
+
>>>
|
673
|
+
>>> f = Filter()
|
674
|
+
>>> f.search('*generic*johnson*')
|
675
|
+
['Generic/Johnson.U',
|
676
|
+
'Generic/Johnson.B',
|
677
|
+
'Generic/Johnson.V',
|
678
|
+
'Generic/Johnson.R',
|
679
|
+
'Generic/Johnson.I',
|
680
|
+
'Generic/Johnson.J',
|
681
|
+
'Generic/Johnson.M']
|
682
|
+
"""
|
683
|
+
self.logger.info(
|
684
|
+
f"BEGIN search - Searching for filters with pattern='{name}', "
|
685
|
+
f"case_sensitive={case}"
|
686
|
+
)
|
687
|
+
|
688
|
+
if not isinstance(name, (type(None), str)):
|
689
|
+
self.logger.error(f"Invalid name type: {type(name)}")
|
690
|
+
raise TypeError('`name` must be `str` or `None` type.')
|
691
|
+
|
692
|
+
if self._catalog is None:
|
693
|
+
self.logger.debug("Loading filter catalog")
|
694
|
+
self._prepare()
|
695
|
+
|
696
|
+
df = self._catalog
|
697
|
+
|
698
|
+
if name is None:
|
699
|
+
result = df['filterID'].to_list()
|
700
|
+
self.logger.info(f"END search - Returning all {len(result)} filters")
|
701
|
+
return result
|
702
|
+
|
703
|
+
self.logger.debug(f"Applying filter pattern matching")
|
704
|
+
if case:
|
705
|
+
mask = df['filterID'].apply(fnmatch, args=(name.strip(),))
|
706
|
+
else:
|
707
|
+
mask = df['filterID'].str.lower().apply(
|
708
|
+
fnmatch,
|
709
|
+
args=(name.strip().lower(),)
|
710
|
+
)
|
711
|
+
|
712
|
+
result = df[mask]['filterID'].to_list()
|
713
|
+
self.logger.info(f"END search - Found {len(result)} matching filters")
|
714
|
+
return result
|
715
|
+
|
716
|
+
def mag_to_flux(
|
717
|
+
self,
|
718
|
+
mag: float,
|
719
|
+
unit: u.Quantity = u.Jy
|
720
|
+
) -> u.Quantity:
|
721
|
+
"""Convert magnitude to flux density using the filter's zero point.
|
722
|
+
|
723
|
+
Parameters
|
724
|
+
----------
|
725
|
+
mag : float
|
726
|
+
Magnitude value to be converted. The magnitude should be in the
|
727
|
+
filter's native magnitude system (e.g. AB mag, Vega mag, etc).
|
728
|
+
|
729
|
+
unit : astropy.units.Quantity
|
730
|
+
Desired output unit for the flux density.
|
731
|
+
Possible values are u.Jy and u.erg / (u.cm**2 * u.AA * u.s)
|
732
|
+
|
733
|
+
Returns
|
734
|
+
-------
|
735
|
+
flux : astropy.units.Quantity
|
736
|
+
Flux density corresponding to the input magnitude. The returned flux
|
737
|
+
will have units of erg/(s*cm^2*Hz) or Jansky (Jy) depending on the
|
738
|
+
filter's zero point unit.
|
739
|
+
|
740
|
+
Raises
|
741
|
+
------
|
742
|
+
ValueError
|
743
|
+
If the filter's Zero Point Type is not 'Pogson'.
|
744
|
+
TypeError
|
745
|
+
If mag is not a float value.
|
746
|
+
AttributeError
|
747
|
+
If filter data has not been loaded from SVO.
|
748
|
+
|
749
|
+
Notes
|
750
|
+
-----
|
751
|
+
This method uses the filter's zero point to convert magnitude to flux
|
752
|
+
density. It assumes a Pogson magnitude system where:
|
753
|
+
|
754
|
+
flux = ZeroPoint * 10^(-0.4 * magnitude)
|
755
|
+
|
756
|
+
The zero point is obtained from the filter's SVO metadata.
|
757
|
+
|
758
|
+
Examples
|
759
|
+
--------
|
760
|
+
>>> from sedlib import Filter
|
761
|
+
>>> from astropy import units as u
|
762
|
+
>>>
|
763
|
+
>>> # Initialize Johnson V filter
|
764
|
+
>>> f = Filter('Generic/Johnson.V')
|
765
|
+
>>>
|
766
|
+
>>> # Convert V=15 mag to flux density
|
767
|
+
>>> flux = f.mag_to_flux(15.0)
|
768
|
+
>>> print(f"{flux:.2e}")
|
769
|
+
"""
|
770
|
+
self.logger.info(
|
771
|
+
f"BEGIN mag_to_flux - Converting magnitude {mag} to flux in units "
|
772
|
+
f"of {unit}"
|
773
|
+
)
|
774
|
+
|
775
|
+
if not self._spec:
|
776
|
+
self.logger.warning("No filter specification available")
|
777
|
+
return None
|
778
|
+
|
779
|
+
if self.ZeroPointType != 'Pogson':
|
780
|
+
self.logger.error(
|
781
|
+
f"Unsupported zero point type: {self.ZeroPointType}"
|
782
|
+
)
|
783
|
+
raise ValueError('The Zero Point Type must be Pogson')
|
784
|
+
|
785
|
+
if not isinstance(mag, (float, int)):
|
786
|
+
self.logger.error(f"Invalid magnitude type: {type(mag)}")
|
787
|
+
raise TypeError('Magnitude must be a float or integer')
|
788
|
+
|
789
|
+
if not isinstance(unit, u.UnitBase):
|
790
|
+
self.logger.error(f"Invalid unit type: {type(unit)}")
|
791
|
+
raise TypeError('`unit` must be an astropy Unit object')
|
792
|
+
|
793
|
+
if unit not in [u.Jy, u.erg / (u.cm**2 * u.AA * u.s)]:
|
794
|
+
self.logger.error(f"Unsupported flux unit: {unit}")
|
795
|
+
raise ValueError(
|
796
|
+
'Unit must be either u.Jy or u.erg / (u.cm**2 * u.AA * u.s)'
|
797
|
+
)
|
798
|
+
|
799
|
+
self.logger.debug("Computing flux from magnitude")
|
800
|
+
flux = self.ZeroPoint * 10 ** (-0.4 * mag)
|
801
|
+
|
802
|
+
if unit != u.Jy:
|
803
|
+
self.logger.debug(f"Converting flux from Jy to {unit}")
|
804
|
+
flux = (
|
805
|
+
2.9979246 *
|
806
|
+
(self.WavelengthRef.value**-2) *
|
807
|
+
1e-5 *
|
808
|
+
flux.value
|
809
|
+
)
|
810
|
+
flux = flux * u.erg / (u.cm**2 * u.AA * u.s)
|
811
|
+
|
812
|
+
self.logger.info(
|
813
|
+
f"END mag_to_flux - Converted magnitude {mag} to flux {flux}"
|
814
|
+
)
|
815
|
+
return flux
|
816
|
+
|
817
|
+
def flux_to_mag(self, flux: u.Quantity) -> float:
|
818
|
+
"""
|
819
|
+
Convert flux density to magnitude using the filter's zero point.
|
820
|
+
|
821
|
+
Parameters
|
822
|
+
----------
|
823
|
+
flux : astropy.units.Quantity
|
824
|
+
Flux density to be converted.
|
825
|
+
The unit must be erg / (cm2 Hz s) or Jy.
|
826
|
+
The flux should be measured through this filter's bandpass.
|
827
|
+
|
828
|
+
Returns
|
829
|
+
-------
|
830
|
+
mag : float
|
831
|
+
Magnitude corresponding to the input flux density in the
|
832
|
+
filter's magnitude system.
|
833
|
+
|
834
|
+
Raises
|
835
|
+
------
|
836
|
+
TypeError
|
837
|
+
If flux is not an astropy Quantity object.
|
838
|
+
ValueError
|
839
|
+
If the filter's Zero Point Type is not 'Pogson'.
|
840
|
+
If the filter data has not been loaded.
|
841
|
+
|
842
|
+
Notes
|
843
|
+
-----
|
844
|
+
This method uses the filter's zero point to convert flux density to
|
845
|
+
magnitude. It assumes a Pogson magnitude system.
|
846
|
+
|
847
|
+
Examples
|
848
|
+
--------
|
849
|
+
>>> from sedlib import Filter
|
850
|
+
>>> from astropy import units as u
|
851
|
+
>>>
|
852
|
+
>>> f = Filter('Generic/Johnson.V')
|
853
|
+
>>> flux = 3.636e-20 * u.erg / (u.cm**2 * u.AA * u.s)
|
854
|
+
>>> mag = f.flux_to_mag(flux)
|
855
|
+
>>> print(f"{mag:.1f}")
|
856
|
+
"""
|
857
|
+
self.logger.info(
|
858
|
+
f"BEGIN flux_to_mag - Converting flux {flux} to magnitude"
|
859
|
+
)
|
860
|
+
|
861
|
+
if not self._spec:
|
862
|
+
self.logger.warning("No filter specification available")
|
863
|
+
return None
|
864
|
+
|
865
|
+
if not isinstance(flux, u.Quantity):
|
866
|
+
self.logger.error(f"Invalid flux type: {type(flux)}")
|
867
|
+
raise TypeError("Flux must be an astropy Quantity")
|
868
|
+
|
869
|
+
if self.ZeroPointType != 'Pogson':
|
870
|
+
self.logger.error(
|
871
|
+
f"Unsupported zero point type: {self.ZeroPointType}"
|
872
|
+
)
|
873
|
+
raise ValueError(
|
874
|
+
f'The Zero Point Type must be Pogson\n'
|
875
|
+
f'Not implemented for {self.ZeroPointType}'
|
876
|
+
)
|
877
|
+
|
878
|
+
self.logger.debug("Computing magnitude from flux")
|
879
|
+
if self.ZeroPoint.unit == flux.unit:
|
880
|
+
mag = -2.5 * np.log10(flux.value / self.ZeroPoint.value)
|
881
|
+
else:
|
882
|
+
self.logger.debug(
|
883
|
+
"Converting flux units before magnitude calculation"
|
884
|
+
)
|
885
|
+
f = ((1 / 2.9979246) * 1e5 *
|
886
|
+
(self.WavelengthRef**2) * flux).value
|
887
|
+
mag = -2.5 * np.log10(f / self.ZeroPoint.value)
|
888
|
+
|
889
|
+
self.logger.info(
|
890
|
+
f"END flux_to_mag - Converted flux to magnitude {mag}"
|
891
|
+
)
|
892
|
+
return mag
|
893
|
+
|
894
|
+
def plot(
|
895
|
+
self,
|
896
|
+
ax: Optional[plt.Axes] = None,
|
897
|
+
figsize: tuple = (12, 5),
|
898
|
+
title: Optional[str] = None,
|
899
|
+
xlabel: Optional[str] = None,
|
900
|
+
ylabel: Optional[str] = None,
|
901
|
+
filename: Optional[str] = None
|
902
|
+
) -> None:
|
903
|
+
"""
|
904
|
+
Plots transmission curve
|
905
|
+
|
906
|
+
Parameters
|
907
|
+
----------
|
908
|
+
ax : matplotlib.axes._axes.Axes or None
|
909
|
+
matplotlib axes
|
910
|
+
|
911
|
+
figsize : tuple
|
912
|
+
figure size
|
913
|
+
|
914
|
+
title : str or None
|
915
|
+
title of plot
|
916
|
+
If title is None, title is filter name
|
917
|
+
|
918
|
+
xlabel : str or None
|
919
|
+
label for the x-axis
|
920
|
+
If label is None, x-axis label is `Wavelength [A]`
|
921
|
+
|
922
|
+
ylabel : str or None
|
923
|
+
label for the y-axis
|
924
|
+
If label is None, y-axis label is `Transmission`
|
925
|
+
|
926
|
+
filename : str or None
|
927
|
+
filename of the transmission curve
|
928
|
+
If filename is None, the curve don't be saved
|
929
|
+
|
930
|
+
Examples
|
931
|
+
--------
|
932
|
+
>>> import matplotlib.pyplot as plt
|
933
|
+
>>> from sedlib import Filter
|
934
|
+
>>>
|
935
|
+
>>> f = Filter('SLOAN/SDSS.gprime_filter')
|
936
|
+
>>>
|
937
|
+
>>> f.plot()
|
938
|
+
>>> plt.show()
|
939
|
+
"""
|
940
|
+
self.logger.info(
|
941
|
+
f"BEGIN plot - Creating transmission curve plot for filter "
|
942
|
+
f"'{self.name}'"
|
943
|
+
)
|
944
|
+
|
945
|
+
fig = None
|
946
|
+
|
947
|
+
if ax is None:
|
948
|
+
self.logger.debug("Creating new figure and axes")
|
949
|
+
fig = plt.figure(figsize=figsize)
|
950
|
+
ax = fig.add_subplot()
|
951
|
+
|
952
|
+
if title is None:
|
953
|
+
title = self.__str__()
|
954
|
+
|
955
|
+
if xlabel is None:
|
956
|
+
xlabel = 'Wavelength [$\\AA$]'
|
957
|
+
|
958
|
+
if ylabel is None:
|
959
|
+
ylabel = 'Transmission'
|
960
|
+
|
961
|
+
self.logger.debug("Setting plot labels and title")
|
962
|
+
ax.set_title(title)
|
963
|
+
ax.set_xlabel(xlabel)
|
964
|
+
ax.set_ylabel(ylabel)
|
965
|
+
|
966
|
+
self.logger.debug("Plotting transmission curve")
|
967
|
+
ax.plot(
|
968
|
+
self.wavelength, self.transmission,
|
969
|
+
'k-', lw=1, label=self.name
|
970
|
+
)
|
971
|
+
|
972
|
+
if hasattr(self, 'WavelengthEff') and hasattr(self, 'WidthEff'):
|
973
|
+
self.logger.debug("Adding effective wavelength and width indicators")
|
974
|
+
ax.axvline(
|
975
|
+
self.WavelengthEff.value, color='green',
|
976
|
+
linestyle='dashed', linewidth=1.5,
|
977
|
+
label=f'Center: {self.WavelengthEff.value: .2f} $\\AA$'
|
978
|
+
)
|
979
|
+
|
980
|
+
ax.axvspan(
|
981
|
+
xmin=self.WavelengthEff.value - self.WidthEff.value / 2,
|
982
|
+
xmax=self.WavelengthEff.value + self.WidthEff.value / 2,
|
983
|
+
color='black', linestyle='--', alpha=0.25,
|
984
|
+
label=f'Eff. Width: {self.WidthEff.value: .2f} $\\AA$'
|
985
|
+
)
|
986
|
+
|
987
|
+
ax.annotate(
|
988
|
+
'', xy=(
|
989
|
+
self.WavelengthEff.value - self.WidthEff.value / 2,
|
990
|
+
self.data(self.WavelengthEff.value) / 2
|
991
|
+
),
|
992
|
+
xytext=(
|
993
|
+
self.WavelengthEff.value + self.WidthEff.value / 2,
|
994
|
+
self.data(self.WavelengthEff.value) / 2
|
995
|
+
),
|
996
|
+
xycoords='data', textcoords='data',
|
997
|
+
arrowprops={
|
998
|
+
'arrowstyle': '<->', 'facecolor': 'cyan'
|
999
|
+
}
|
1000
|
+
)
|
1001
|
+
|
1002
|
+
if hasattr(self, 'WavelengthMin'):
|
1003
|
+
self.logger.debug("Adding minimum wavelength indicator")
|
1004
|
+
ax.axvline(
|
1005
|
+
x=self.WavelengthMin.value, color='blue',
|
1006
|
+
linestyle='dashed', linewidth=1.5,
|
1007
|
+
label=f'Min: {self.WavelengthMin.value: .2f} $\\AA$ [1%]'
|
1008
|
+
)
|
1009
|
+
|
1010
|
+
if hasattr(self, 'WavelengthMax'):
|
1011
|
+
self.logger.debug("Adding maximum wavelength indicator")
|
1012
|
+
ax.axvline(
|
1013
|
+
x=self.WavelengthMax.value, color='red',
|
1014
|
+
linestyle='dashed', linewidth=1.5,
|
1015
|
+
label=f'Max: {self.WavelengthMax.value: .2f} $\\AA$ [1%]'
|
1016
|
+
)
|
1017
|
+
|
1018
|
+
ax.legend()
|
1019
|
+
|
1020
|
+
if filename is not None:
|
1021
|
+
self.logger.debug(f"Saving plot to file: {filename}")
|
1022
|
+
fig.savefig(filename)
|
1023
|
+
|
1024
|
+
self.logger.info(
|
1025
|
+
"END plot - Filter transmission curve plot created successfully"
|
1026
|
+
)
|
1027
|
+
|
1028
|
+
def get_logs(self, log_type: str = 'all') -> Optional[List[str]]:
|
1029
|
+
"""Get all stored log records for this filter instance.
|
1030
|
+
if in-memory logging is enabled, return the log records,
|
1031
|
+
otherwise return None
|
1032
|
+
|
1033
|
+
Parameters
|
1034
|
+
----------
|
1035
|
+
log_type : str
|
1036
|
+
log type to get
|
1037
|
+
possible values are 'all', 'info', 'debug', 'warning', 'error'
|
1038
|
+
(default: 'all')
|
1039
|
+
|
1040
|
+
Returns
|
1041
|
+
-------
|
1042
|
+
Optional[List[str]]
|
1043
|
+
List of formatted log records if in-memory logging is enabled,
|
1044
|
+
None otherwise
|
1045
|
+
"""
|
1046
|
+
if self.memory_handler:
|
1047
|
+
return self.memory_handler.get_logs(log_type)
|
1048
|
+
return None
|
1049
|
+
|
1050
|
+
def dump_logs(self, filename: str) -> None:
|
1051
|
+
"""Dump all stored log records for this filter instance to a file.
|
1052
|
+
if in-memory logging is enabled, dump the log records to a file,
|
1053
|
+
otherwise do nothing
|
1054
|
+
"""
|
1055
|
+
if self.memory_handler:
|
1056
|
+
self.memory_handler.dump_logs(filename)
|
1057
|
+
|
1058
|
+
def clear_logs(self) -> None:
|
1059
|
+
"""Clear all stored log records for this filter instance.
|
1060
|
+
if in-memory logging is enabled, clear the log records,
|
1061
|
+
otherwise do nothing
|
1062
|
+
"""
|
1063
|
+
if self.memory_handler:
|
1064
|
+
self.memory_handler.clear()
|