processconfig.py 9.26 KB
Newer Older
Pierre Paleo's avatar
Pierre Paleo committed
1
import os
Pierre Paleo's avatar
Pierre Paleo committed
2
from .config import parse_nabu_config_file
Pierre Paleo's avatar
Pierre Paleo committed
3
4
from ..utils import deprecation_warning
from ..resources.logger import Logger, PrinterLogger
5
from .config import validate_config
6
from ..resources.dataset_analyzer import analyze_dataset, _tomoscan_has_nxversion
7
8
from .estimators import DetectorTiltEstimator

Pierre Paleo's avatar
Pierre Paleo committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
class ProcessConfigBase:
    """
    A class for describing the Nabu process configuration.
    """
    # Must be overriden by inheriting class
    default_nabu_config = None
    config_renamed_keys = None

    def __init__(
        self,
        conf_fname=None,
        conf_dict=None,
        dataset_infos=None,
        dataset_info=None,
        checks=True,
        remove_unused_radios=True,
        create_logger=False,
    ):
        """
        Initialize a ProcessConfig class.

        Parameters
        ----------
        conf_fname: str
            Path to the nabu configuration file. If provided, the parameters
            `conf_dict` is ignored.
        conf_dict: dict
            A dictionary describing the nabu processing steps.
            If provided, the parameter `conf_fname` is ignored.
        dataset_info: DatasetAnalyzer
            A `DatasetAnalyzer` class instance.
        checks: bool, optional, default is True
            Whether to perform checks on configuration and datasets (recommended !)
        remove_unused_radios: bool, optional, default is True
            Whether to remove unused radios, i.e radios present in the dataset,
            but not explicitly listed in the scan metadata.
        create_logger: str or bool, optional
            Whether to create a Logger object. Default is False, meaning that the logger
            object creation is left to the user.
            If set to True, a Logger object is created, and logs will be written
            to the file "nabu_dataset_name.log".
            If set to a string, a Logger object is created, and the logs will be written
            to the file specified by this string.
        """
        # COMPAT.
        if dataset_infos is not None:
            deprecation_warning(
Pierre Paleo's avatar
Pierre Paleo committed
56
57
                "The parameter 'dataset_infos' will be renamed 'dataset_info' in a future version",
                func_name="processconfig_dataset_infos"
Pierre Paleo's avatar
Pierre Paleo committed
58
59
60
            )
        if (dataset_infos is not None) and (dataset_info is not None):
            raise ValueError("Please provide either 'dataset_info' or 'dataset_infos'")
Pierre Paleo's avatar
Pierre Paleo committed
61
        dataset_info = dataset_info or dataset_infos
62
63
64
65
66
        # TODO add this in doc.
        # deprecation_warning(
            # "The parameters 'checks' and 'remove_unused_radios' are not used anymore and will be removed in a future version",
            # func_name='processconfig_checks'
        # )
Pierre Paleo's avatar
Pierre Paleo committed
67
68
        # ---

Pierre Paleo's avatar
Pierre Paleo committed
69
        # Step (1a): create 'nabu_config'
70
        self._parse_configuration(conf_fname, conf_dict)
Pierre Paleo's avatar
Pierre Paleo committed
71
72
        self._create_logger(create_logger)

Pierre Paleo's avatar
Pierre Paleo committed
73
        # Step (1b): create 'dataset_info'
74
        self._browse_dataset(dataset_info)
Pierre Paleo's avatar
Pierre Paleo committed
75
76

        # Step (2a)
77
        self._update_dataset_info_with_user_config()
Pierre Paleo's avatar
Pierre Paleo committed
78

Pierre Paleo's avatar
Pierre Paleo committed
79
        # Step (3): estimate tilt, CoR, ...
80
        self._dataset_estimations()
Pierre Paleo's avatar
Pierre Paleo committed
81
82

        # Step (4)
83
        self._coupled_validation()
Pierre Paleo's avatar
Pierre Paleo committed
84
85

        # Step (5)
86
        self._build_processing_steps()
Pierre Paleo's avatar
Pierre Paleo committed
87

88
89
90
91
        # Step (6)
        self._configure_save_steps()
        self._configure_resume()

Pierre Paleo's avatar
Pierre Paleo committed
92

93
94
95
96
97
98
99
100
101
    # COMPAT.
    @property
    def dataset_infos(self):
        deprecation_warning(
            "The name 'dataset_infos' is deprecated. Please use 'dataset_info'.",
            func_name="processconfig_dataset_infos_attr"
        )
        return self.dataset_info
    # ---
Pierre Paleo's avatar
Pierre Paleo committed
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130

    def _create_logger(self, create_logger):
        if create_logger is False:
            self.logger = PrinterLogger()
            return
        elif create_logger is True:
            dataset_loc = self.nabu_config["dataset"]["location"]
            dataset_fname_rel = os.path.basename(dataset_loc)
            if os.path.isfile(dataset_loc):
                logger_filename = os.path.join(
                    os.path.abspath(os.getcwd()),
                    os.path.splitext(dataset_fname_rel)[0] + "_nabu.log"
                )
            else:
                logger_filename = os.path.join(
                    os.path.abspath(os.getcwd()),
                    dataset_fname_rel + "_nabu.log"
                )
        elif isinstance(create_logger, str):
            logger_filename = create_logger
        else:
            raise ValueError("Expected bool or str for create_logger")
        self.logger = Logger(
            "nabu",
            level=self.nabu_config["pipeline"]["verbosity"],
            logfile=logger_filename
        )


131
    def _parse_configuration(self, conf_fname, conf_dict):
Pierre Paleo's avatar
Pierre Paleo committed
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
        """
        Parse the user configuration and builds a dictionary.

        Parameters
        ----------
        conf_fname: str
            Path to the .conf file. Mutually exclusive with 'conf_dict'
        conf_dict: dict
            Dictionary with the configuration. Mutually exclusive with 'conf_fname'
        """
        if not((conf_fname is None) ^ (conf_dict is None)):
            raise ValueError("You must either provide 'conf_fname' or 'conf_dict'")
        if conf_fname is not None:
            if not os.path.isfile(conf_fname):
                raise ValueError("No such file: %s" % conf_fname)
            self.conf_fname = conf_fname
            self.conf_dict = parse_nabu_config_file(conf_fname)
        else:
            self.conf_dict = conf_dict
        if self.default_nabu_config is None or self.config_renamed_keys is None:
            raise ValueError(
                "'default_nabu_config' and 'config_renamed_keys' must be specified by classes inheriting from ProcessConfig"
            )
155
        self.nabu_config = validate_config(
Pierre Paleo's avatar
Pierre Paleo committed
156
157
158
159
160
161
            self.conf_dict,
            self.default_nabu_config,
            self.config_renamed_keys,
        )


162
    def _browse_dataset(self, dataset_info):
Pierre Paleo's avatar
Pierre Paleo committed
163
        """
Pierre Paleo's avatar
Pierre Paleo committed
164
        Browse a dataset and builds a data structure with the relevant information.
Pierre Paleo's avatar
Pierre Paleo committed
165
        """
Pierre Paleo's avatar
Pierre Paleo committed
166
        self.logger.debug("Browsing dataset")
Pierre Paleo's avatar
Pierre Paleo committed
167
168
169
        if dataset_info is not None:
            self.dataset_info = dataset_info
        else:
170
171
172
173
174
175
176
177
            extra_options = {
                "exclude_projections": self.nabu_config["dataset"]["exclude_projections"],
                "hdf5_entry": self.nabu_config["dataset"]["hdf5_entry"],
            }
            if _tomoscan_has_nxversion: # legacy - should become "if True" soon
                extra_options["nx_version"] = self.nabu_config["dataset"]["nexus_version"]
            else:
                self.logger.warning("Cannot use 'nx_version' for browsing dataset: need tomoscan > 0.6.0")
178
            self.dataset_info = analyze_dataset(
Pierre Paleo's avatar
Pierre Paleo committed
179
                self.nabu_config["dataset"]["location"],
180
                extra_options=extra_options,
Pierre Paleo's avatar
Pierre Paleo committed
181
182
183
184
                logger=self.logger
            )


185
    def _update_dataset_info_with_user_config(self):
Pierre Paleo's avatar
Pierre Paleo committed
186
187
188
        """
        Update the 'dataset_info' (DatasetAnalyzer class instance) data structure with options from user configuration.
        """
189
        raise ValueError("Base class")
Pierre Paleo's avatar
Pierre Paleo committed
190

191

192
193
194
    def _get_rotation_axis_position(self):
        self.dataset_info.axis_position = self.nabu_config["reconstruction"]["rotation_axis_position"]

195

Pierre Paleo's avatar
Pierre Paleo committed
196
    def _update_rotation_angles(self):
197
        raise ValueError("Base class")
Pierre Paleo's avatar
Pierre Paleo committed
198
199


200
    def _dataset_estimations(self):
Pierre Paleo's avatar
Pierre Paleo committed
201
202
203
        """
        Perform estimation of several parameters like center of rotation and detector tilt angle.
        """
Pierre Paleo's avatar
Pierre Paleo committed
204
        self.logger.debug("Doing dataset estimations")
Pierre Paleo's avatar
Pierre Paleo committed
205
206
207
        self._get_tilt()
        self._get_cor()

Pierre Paleo's avatar
Pierre Paleo committed
208

Pierre Paleo's avatar
Pierre Paleo committed
209
    def _get_cor(self):
210
        raise ValueError("Base class")
Pierre Paleo's avatar
Pierre Paleo committed
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232


    def _get_tilt(self):
        tilt = self.nabu_config["preproc"]["tilt_correction"]
        user_rot_projs = self.nabu_config["preproc"]["rotate_projections"]
        if user_rot_projs is not None and tilt is not None:
            msg = "=" * 80 + "\n"
            msg += "Both 'detector_tilt' and 'rotate_projections' options were provided. The option 'rotate_projections' will take precedence. This means that the projections will be rotated by %f degrees and the option 'detector_tilt' will be ignored." % user_rot_projs
            msg += "\n" + "=" * 80
            self.logger.warning(msg)
            tilt = user_rot_projs
        #
        if isinstance(tilt, str): # auto-tilt
            self.tilt_estimator = DetectorTiltEstimator(
                self.dataset_info,
                logger=self.logger,
                autotilt_options=self.nabu_config["preproc"]["autotilt_options"]
            )
            tilt = self.tilt_estimator.find_tilt(tilt_method=tilt)
        self.dataset_info.detector_tilt = tilt


233
234
235
236
237
    def _coupled_validation(self):
        """
        Validate together the dataset information and user configuration.
        Update 'dataset_info' and 'nabu_config'
        """
Pierre Paleo's avatar
Pierre Paleo committed
238
239
240
        raise ValueError("Base class")


241
    def _build_processing_steps(self):
Pierre Paleo's avatar
Pierre Paleo committed
242
243
244
245
246
        """
        Build the processing steps, i.e a tuple (steps, options) where
           - steps is a list of str (list of processing steps names)
           - options is a dict with processing options
        """
247
        raise ValueError("Base class")
Pierre Paleo's avatar
Pierre Paleo committed
248
249
250


    build_processing_steps = _build_processing_steps # COMPAT.
251
252
253
254
255
256
257
258
259
260


    def _configure_save_steps(self):
        raise ValueError("Base class")


    def _configure_resume(self):
        raise ValueError("Base class")