Coverage for src/susi/base/config/config.py: 89%

66 statements  

« prev     ^ index     » next       coverage.py v7.5.0, created at 2025-06-13 14:15 +0000

1#!/usr/bin/env python3 

2# -*- coding: utf-8 -*- 

3""" 

4Module for configuration matters 

5 

6@author: hoelken, iglesias, feller 

7""" 

8from datetime import datetime 

9import yaml 

10import os 

11from ..logging import Logging 

12 

13from .base import Base 

14from .calibdata import CalibData 

15from .cam import Cam 

16from .data import Data 

17from .spectropol import SpectroPol 

18from .reduc import Reduc 

19 

20log = Logging.get_logger() 

21 

22 

23class Config: 

24 """ 

25 DataItem for configuration. 

26 

27 It's main purpose is to configure the source for the processing. 

28 See documentation of instance variables for configuration options available. 

29 """ 

30 

31 def __init__(self): 

32 #: Base configuration 

33 self.base: Base = Base() 

34 #: Definition where to find the data and where to write it to 

35 self.data: Data = Data() 

36 #: Camera specific configuration 

37 self.cam: Cam = Cam.with_defaults('cam1') 

38 #: Paths to calibration data as darks and flats 

39 self.calib_data: CalibData = CalibData() 

40 #: Configuration for the Spectro-Polarimeter 

41 self.spol: SpectroPol = SpectroPol() 

42 #: Configuration for other reduction stpes 

43 self.reduc: Reduc = Reduc() 

44 #: start time of the process 

45 self.start: datetime = None 

46 #: end time of the process 

47 self.stop: datetime = None 

48 

49 def cam_defaults(self, c_name: str) -> None: 

50 """Load the camera specific defaults for the given camera""" 

51 self.cam = Cam.with_defaults(c_name) 

52 

53 @staticmethod 

54 def set_log_level(level: int) -> None: 

55 """Change the logging log level""" 

56 Logging.set_log_level(level) 

57 

58 @staticmethod 

59 def from_yaml(file_path: str): 

60 """ 

61 Create a config from a yaml file. 

62 

63 The file must specify a `config` section. All instance variable names 

64 are supported as keywords in this section. 

65 The `config` section is parsed as python dictionary which is then 

66 used to create the Configuration. See `from_dict` for details. 

67 

68 ### Params 

69 - file_path: the location of the config file 

70 

71 ### Returns 

72 the created Config 

73 """ 

74 with open(file_path, "r") as stream: 

75 data = yaml.safe_load(stream) 

76 conf = Config() 

77 if 'config' in data: 

78 if 'cam' in data['config'] and 'name' in data['config']['cam']: 

79 c = data['config']['cam']['name'] 

80 else: 

81 c = 'cam1' 

82 conf = Config.from_dict(data['config'], c) 

83 for c in ['cam1', 'cam2', 'cam3']: 

84 if c in data: 

85 conf = Config.from_dict(data[c], c) 

86 if 'start' in data: 

87 conf.start = datetime.fromisoformat(data['start']) 

88 if 'stop' in data: 

89 conf.start = datetime.fromisoformat(data['stop']) 

90 return conf 

91 

92 @staticmethod 

93 def from_dict(data: dict, c_name: str = 'cam1'): 

94 """ 

95 Create a config from a dictionary. 

96 

97 All instance variable names are supported as keywords. 

98 All keywords are optional, if the keyword is not present the default will be used. 

99 There are some special keys that must follow a specific syntax if specified 

100 

101 - 'data_shape': The data shape must be given as a dictionary with keys 'x' and 'y' 

102 for vertical and horizontal shape. The values of both keys must be lists 

103 of integers of length 2, e.g. `{'x': [1, 2], 'y': [3, 4]}` 

104 

105 - 'roi_keys': The Region Of Interest (ROI) keys must be given as a dictionary with 

106 arbitrary keys. The values of both keys must be lists of length 2. 

107 Example `{'x': ['X0', 'X1'], 'y': ['Y0', 'Y1'], 'lambda': ['L0', 'L1']}` 

108 

109 ### Params 

110 - data: The dictionary to parse. 

111 - c_name: The name of the camera to set defaults for. 

112 

113 ### Returns 

114 the created Config 

115 """ 

116 config = Config() 

117 config.cam_defaults(c_name) 

118 for t in config.__dict__.keys(): 

119 if t in data: 

120 getattr(config, t).amend_from_dict(data[t]) 

121 return config 

122 

123 def input_pattern(self) -> str: 

124 """ 

125 Generate the lookup pattern for the input values 

126 

127 ### Returns 

128 A glob pattern to look up input files. 

129 """ 

130 return os.path.join( 

131 self.data.root, self.data.level, self.data.dataset, self.cam.name, self.data.pattern + self.data.ext 

132 ) 

133 

134 def is_cam3(self): 

135 return self.cam.name == self.cam.C3 

136 

137 def __repr__(self) -> str: 

138 txt = 'Current SUSI Datareduction config\n' 

139 for t in self.__dict__.keys(): 

140 txt += repr(getattr(self, t)) + '\n' 

141 return txt 

142 

143 def info(self) -> None: 

144 """Write the current config to the log""" 

145 log.info(repr(self)) 

146 

147 def out_path(self): 

148 """Get the out path to use""" 

149 return self.data.out_path if self.data.out_path else os.path.expanduser('~')