|
a |
|
b/dosma/defaults.py |
|
|
1 |
"""Default parameters. |
|
|
2 |
|
|
|
3 |
This is the first file that is imported from this software. |
|
|
4 |
All initialization imports should occur here. |
|
|
5 |
|
|
|
6 |
Please change depending on your preferences. |
|
|
7 |
""" |
|
|
8 |
import os |
|
|
9 |
import shutil |
|
|
10 |
import warnings |
|
|
11 |
import yaml |
|
|
12 |
from typing import Any, Mapping |
|
|
13 |
|
|
|
14 |
from dosma.utils import env |
|
|
15 |
|
|
|
16 |
import matplotlib |
|
|
17 |
import nested_lookup |
|
|
18 |
|
|
|
19 |
__all__ = ["preferences"] |
|
|
20 |
|
|
|
21 |
# Parse preferences file |
|
|
22 |
_resources_dir = env.resources_dir() |
|
|
23 |
_internal_preferences_template_filepath = os.path.join(_resources_dir, "templates/.preferences.yml") |
|
|
24 |
_preferences_cmd_line_filepath = os.path.join( |
|
|
25 |
_resources_dir, "templates/.preferences_cmd_line_schema.yml" |
|
|
26 |
) |
|
|
27 |
|
|
|
28 |
__preferences_filepath__ = os.path.join(_resources_dir, "preferences.yml") |
|
|
29 |
|
|
|
30 |
if not os.path.isfile(__preferences_filepath__): |
|
|
31 |
shutil.copyfile(_internal_preferences_template_filepath, __preferences_filepath__) |
|
|
32 |
|
|
|
33 |
# Default rounding for I/O (dicom, nifti, etc) - DO NOT CHANGE |
|
|
34 |
AFFINE_DECIMAL_PRECISION = 4 |
|
|
35 |
SCANNER_ORIGIN_DECIMAL_PRECISION = 4 |
|
|
36 |
|
|
|
37 |
DEFAULT_FONT_SIZE = 16 |
|
|
38 |
DEFAULT_TEXT_SPACING = DEFAULT_FONT_SIZE * 0.01 |
|
|
39 |
|
|
|
40 |
|
|
|
41 |
class _Preferences(object): |
|
|
42 |
"""A pseudo-Singleton class implementation to track preferences. |
|
|
43 |
|
|
|
44 |
Do not instantiate this class. To modify/update preferences use the ``preferences`` |
|
|
45 |
module variable defined below. |
|
|
46 |
|
|
|
47 |
However, in the case this class is instantiated, a new object will be created, |
|
|
48 |
but all config properties will be shared among all instances. Meaning modifying |
|
|
49 |
the config in one instance will impact the preferences state in another instance. |
|
|
50 |
""" |
|
|
51 |
|
|
|
52 |
_template_filepath = _internal_preferences_template_filepath |
|
|
53 |
_preferences_filepath = __preferences_filepath__ |
|
|
54 |
__config = {} |
|
|
55 |
__key_list = [] |
|
|
56 |
|
|
|
57 |
def __init__(self): |
|
|
58 |
# Load config and set information if config has not been initialized. |
|
|
59 |
if not self.__config: |
|
|
60 |
with open(self._preferences_filepath, "r") as f: |
|
|
61 |
self.__config = yaml.safe_load(f) |
|
|
62 |
with open(self._template_filepath, "r") as f: |
|
|
63 |
template = yaml.safe_load(f) |
|
|
64 |
|
|
|
65 |
self.__config = self._merge_dicts(self.__config, template) |
|
|
66 |
|
|
|
67 |
matplotlib.rcParams.update(self.__config["visualization"]["matplotlib"]["rcParams"]) |
|
|
68 |
|
|
|
69 |
# Store all preference keys. |
|
|
70 |
self.__key_list = self._unroll_keys(self.config, "") |
|
|
71 |
|
|
|
72 |
def _merge_dicts(self, target, base, prefix=""): |
|
|
73 |
"""Merges dicts from target onto base.""" |
|
|
74 |
target_keys = target.keys() |
|
|
75 |
base_keys = base.keys() |
|
|
76 |
all_keys = target_keys | base_keys |
|
|
77 |
|
|
|
78 |
for k in all_keys: |
|
|
79 |
prefix_k = f"{prefix}/{k}" if prefix else k |
|
|
80 |
|
|
|
81 |
if k in target_keys and k not in base_keys: |
|
|
82 |
# Allow target config to specify parameters not in the base config. |
|
|
83 |
# Note this may change in the future. |
|
|
84 |
pass |
|
|
85 |
elif k not in target_keys and k in base_keys: |
|
|
86 |
warnings.warn( |
|
|
87 |
"Your preferences file may be outdated. " |
|
|
88 |
"Run `preferences.save()` to save your updated preferences." |
|
|
89 |
) |
|
|
90 |
target[k] = base[k] |
|
|
91 |
else: |
|
|
92 |
target_v = target[k] |
|
|
93 |
template_v = base[k] |
|
|
94 |
|
|
|
95 |
if isinstance(target_v, Mapping) and isinstance(template_v, Mapping): |
|
|
96 |
target[k] = self._merge_dicts(target_v, template_v, prefix=prefix_k) |
|
|
97 |
elif isinstance(template_v, Mapping): |
|
|
98 |
raise ValueError( |
|
|
99 |
f"{prefix_k}: Got target type '{type(target_v)}', " |
|
|
100 |
f"but base config type is '{type(template_v)}'" |
|
|
101 |
) |
|
|
102 |
else: |
|
|
103 |
# If both are not mapping, keep the target value. |
|
|
104 |
pass |
|
|
105 |
|
|
|
106 |
return target |
|
|
107 |
|
|
|
108 |
def _unroll_keys(self, subdict: dict, prefix: str) -> list: |
|
|
109 |
"""Recursive method to unroll keys.""" |
|
|
110 |
pref_keys = [] |
|
|
111 |
keys = subdict.keys() |
|
|
112 |
for k in keys: |
|
|
113 |
prefix_or_key = "{}/{}".format(prefix, k) |
|
|
114 |
val = subdict[k] |
|
|
115 |
if type(val) is dict: |
|
|
116 |
pref_keys.extend(self._unroll_keys(val, prefix_or_key)) |
|
|
117 |
else: |
|
|
118 |
pref_keys.append(prefix_or_key) |
|
|
119 |
|
|
|
120 |
return pref_keys |
|
|
121 |
|
|
|
122 |
@staticmethod |
|
|
123 |
def _get_prefixes(prefix: str = ""): |
|
|
124 |
if not prefix: |
|
|
125 |
return () |
|
|
126 |
|
|
|
127 |
r_prefixes = [] |
|
|
128 |
prefixes = prefix.split("/") |
|
|
129 |
for p in prefixes: |
|
|
130 |
if p == "": |
|
|
131 |
continue |
|
|
132 |
r_prefixes.append(p) |
|
|
133 |
|
|
|
134 |
return r_prefixes |
|
|
135 |
|
|
|
136 |
def get(self, key: str, prefix: str = ""): |
|
|
137 |
"""Get preference. |
|
|
138 |
|
|
|
139 |
Args: |
|
|
140 |
key (str): Preference to peek. Can be full path preference. |
|
|
141 |
prefix (str, optional): Prefix defining which sub-config to search. |
|
|
142 |
For example, to access the key ``"visualization/rcParams"``, this parameter |
|
|
143 |
should be ``"visualization"``. |
|
|
144 |
|
|
|
145 |
Returns: |
|
|
146 |
The preference value. |
|
|
147 |
""" |
|
|
148 |
return self.__get(self.__config, key, prefix)[0] |
|
|
149 |
|
|
|
150 |
def __get(self, b_dict: dict, key: str, prefix: str = ""): |
|
|
151 |
"""Get preference. |
|
|
152 |
|
|
|
153 |
Args: |
|
|
154 |
b_dict (dict): Dictionary to search. |
|
|
155 |
key (str): Preference to peek. Can be full path preference. |
|
|
156 |
prefix (:obj:`str`, optional): Prefix defining which sub-config to search. |
|
|
157 |
For example, to access the key ``"visualization/rcParams"``, |
|
|
158 |
this parameter should be ``"visualization"``. |
|
|
159 |
|
|
|
160 |
Returns: |
|
|
161 |
The preference value, sub-dictionary to search |
|
|
162 |
""" |
|
|
163 |
p_keys = self._get_prefixes(key) |
|
|
164 |
key = p_keys[-1] |
|
|
165 |
k_prefixes = list(p_keys[:-1]) |
|
|
166 |
|
|
|
167 |
prefixes = list(self._get_prefixes(prefix)) |
|
|
168 |
prefixes.extend(k_prefixes) |
|
|
169 |
|
|
|
170 |
subdict = b_dict |
|
|
171 |
for p in prefixes: |
|
|
172 |
subdict = subdict[p] |
|
|
173 |
|
|
|
174 |
num_keys = nested_lookup.get_occurrence_of_key(subdict, key) |
|
|
175 |
|
|
|
176 |
if num_keys == 0: |
|
|
177 |
raise KeyError("Key not '%s ' found in prefix '%s'" % (key, prefix)) |
|
|
178 |
if num_keys > 1: |
|
|
179 |
raise KeyError( |
|
|
180 |
"Multiple keys '%s 'found in prefix '%s'. Provide a more specific prefix." |
|
|
181 |
% (key, prefix) |
|
|
182 |
) |
|
|
183 |
|
|
|
184 |
return nested_lookup.nested_lookup(key, subdict)[0], subdict |
|
|
185 |
|
|
|
186 |
def set(self, key: str, value: Any, prefix: str = ""): |
|
|
187 |
"""Set preference. |
|
|
188 |
|
|
|
189 |
Args: |
|
|
190 |
key (str): Preference to peek. Can be full path preference. |
|
|
191 |
value (Any): Value to set preference. |
|
|
192 |
prefix (str, optional): Prefix defining which sub-config to search. |
|
|
193 |
For example, to access the key ``visualization/rcParams``, |
|
|
194 |
this parameter should be `visualization`. Defaults to ''. |
|
|
195 |
|
|
|
196 |
Returns: |
|
|
197 |
The preference value. |
|
|
198 |
""" |
|
|
199 |
val, subdict = self.__get(self.__config, key, prefix) |
|
|
200 |
|
|
|
201 |
# type of new value has to be the same type as old value |
|
|
202 |
if type(value) != type(val): |
|
|
203 |
try: |
|
|
204 |
value = type(val)(value) |
|
|
205 |
except (ValueError, TypeError): |
|
|
206 |
raise TypeError("Could not convert %s to %s: %s" % (type(value), type(val), value)) |
|
|
207 |
|
|
|
208 |
p_keys = self._get_prefixes(key) |
|
|
209 |
key = p_keys[-1] |
|
|
210 |
nested_lookup.nested_update(subdict, key, value, in_place=True) |
|
|
211 |
|
|
|
212 |
# if param is an rcParam, update matplotlib |
|
|
213 |
if key in self.config["visualization"]["matplotlib"]["rcParams"].keys(): |
|
|
214 |
matplotlib.rcParams.update({key: value}) |
|
|
215 |
|
|
|
216 |
def save(self): |
|
|
217 |
"""Save preferences to file. |
|
|
218 |
|
|
|
219 |
Args: |
|
|
220 |
file_path (str, optional): File path to yml file. |
|
|
221 |
Defaults to local preferences file. |
|
|
222 |
""" |
|
|
223 |
with open(self._preferences_filepath, "w") as f: |
|
|
224 |
yaml.dump(self.__config, f, default_flow_style=False) |
|
|
225 |
|
|
|
226 |
@property |
|
|
227 |
def config(self): |
|
|
228 |
"""Get preferences configuration.""" |
|
|
229 |
return self.__config |
|
|
230 |
|
|
|
231 |
# Make certain preferences easily accessible through this class. |
|
|
232 |
@property |
|
|
233 |
def segmentation_batch_size(self) -> int: |
|
|
234 |
"""int: Batch size for segmentation models.""" |
|
|
235 |
return self.get("/segmentation/batch.size") |
|
|
236 |
|
|
|
237 |
@property |
|
|
238 |
def visualization_use_vmax(self) -> bool: |
|
|
239 |
return self.get("/visualization/use.vmax") |
|
|
240 |
|
|
|
241 |
@property |
|
|
242 |
def mask_dilation_rate(self) -> float: |
|
|
243 |
"""float: rate for mask dilation.""" |
|
|
244 |
return self.get("/registration/mask/dilation.rate") |
|
|
245 |
|
|
|
246 |
@property |
|
|
247 |
def mask_dilation_threshold(self) -> float: |
|
|
248 |
"""float: threshold for mask dilation.""" |
|
|
249 |
return self.get("/registration/mask/dilation.threshold") |
|
|
250 |
|
|
|
251 |
@property |
|
|
252 |
def fitting_r2_threshold(self) -> float: |
|
|
253 |
"""float: r2 threshold for fitting""" |
|
|
254 |
return self.get("/fitting/r2.threshold") |
|
|
255 |
|
|
|
256 |
@property |
|
|
257 |
def image_data_format(self): |
|
|
258 |
"""ImageDataFormat: Format for images (e.g. png, eps, etc.).""" |
|
|
259 |
from dosma.core.io.format_io import ImageDataFormat |
|
|
260 |
|
|
|
261 |
return ImageDataFormat[self.get("/data/format")] |
|
|
262 |
|
|
|
263 |
@property |
|
|
264 |
def nipype_logging(self) -> str: |
|
|
265 |
"""str: nipype library logging mode.""" |
|
|
266 |
# TODO: Remove this try/except clause when schema is well defined. |
|
|
267 |
try: |
|
|
268 |
return self.get("/logging/nipype") |
|
|
269 |
except KeyError: |
|
|
270 |
return "file_stderr" |
|
|
271 |
|
|
|
272 |
def cmd_line_flags(self) -> dict: |
|
|
273 |
"""Provide command line flags for changing preferences via command line. |
|
|
274 |
|
|
|
275 |
Not all preferences are listed here. Only those that should easily be set. |
|
|
276 |
|
|
|
277 |
All default values will be based on the current state of preferences, |
|
|
278 |
not the static state specified in yaml file. |
|
|
279 |
|
|
|
280 |
Returns: |
|
|
281 |
Preference keys with corresponding argparse kwarg dict |
|
|
282 |
""" |
|
|
283 |
with open(_preferences_cmd_line_filepath) as f: |
|
|
284 |
cmd_line_config = yaml.safe_load(f) |
|
|
285 |
|
|
|
286 |
cmd_line_dict = {} |
|
|
287 |
for k in self.__key_list: |
|
|
288 |
try: |
|
|
289 |
argparse_kwargs, _ = self.__get(cmd_line_config, k) |
|
|
290 |
except KeyError: |
|
|
291 |
continue |
|
|
292 |
|
|
|
293 |
argparse_kwargs["default"] = self.get(k) |
|
|
294 |
argparse_kwargs["type"] = eval(argparse_kwargs["type"]) |
|
|
295 |
cmd_line_dict[k] = argparse_kwargs |
|
|
296 |
|
|
|
297 |
return cmd_line_dict |
|
|
298 |
|
|
|
299 |
def __str__(self): |
|
|
300 |
return str(self.__config) |
|
|
301 |
|
|
|
302 |
|
|
|
303 |
preferences = _Preferences() |