Test Failed
Pull Request — master (#878)
by Daniil
03:48
created

savu.plugins.plugin   C

Complexity

Total Complexity 54

Size/Duplication

Total Lines 325
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 160
dl 0
loc 325
rs 6.4799
c 0
b 0
f 0
wmc 54

38 Methods

Rating   Name   Duplication   Size   Complexity  
A Plugin.nFrames() 0 4 1
A Plugin.executive_summary() 0 10 1
A Plugin.setup() 0 8 1
A Plugin.__clean_up_plugin_data() 0 8 2
A Plugin.set_preview() 0 15 3
A Plugin.__copy_meta_data() 0 19 5
A Plugin.set_parameters() 0 2 1
A Plugin._set_plugin_tools() 0 4 1
A Plugin.initialise() 0 5 1
A Plugin.base_pre_process() 0 5 1
A Plugin.post_process() 0 10 1
A Plugin.base_process_frames_before() 0 3 1
A Plugin.base_process_frames_after() 0 4 1
A Plugin.get_process_frames_counter() 0 2 1
A Plugin.set_filter_padding() 0 6 1
A Plugin.get_plugin_tools() 0 2 1
A Plugin._reset_process_frames_counter() 0 2 1
A Plugin.get_parameters() 0 8 1
A Plugin.get_global_frame_index() 0 3 1
A Plugin.get_current_slice_list() 0 3 1
A Plugin.process_frames() 0 11 1
A Plugin.nClone_datasets() 0 5 1
A Plugin.pre_process() 0 3 1
A Plugin.final_parameter_updates() 0 4 1
A Plugin.__remove_axis_data() 0 17 3
A Plugin.base_post_process() 0 5 2
A Plugin.__init__() 0 12 1
A Plugin.plugin_process_frames() 0 7 2
A Plugin.delete_parameter_entry() 0 3 2
A Plugin.get_slice_dir_reps() 0 10 2
A Plugin.set_global_frame_index() 0 2 1
A Plugin.nOutput_datasets() 0 8 1
A Plugin.nInput_datasets() 0 8 1
A Plugin._clean_up() 0 7 1
A Plugin._revert_preview() 0 8 3
A Plugin.__set_previous_patterns() 0 4 2
A Plugin.set_current_slice_list() 0 2 1
A Plugin._main_setup() 0 14 1

How to fix   Complexity   

Complexity

Complex classes like savu.plugins.plugin often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

1
# Copyright 2014 Diamond Light Source Ltd.
2
#
3
# Licensed under the Apache License, Version 2.0 (the "License");
4
# you may not use this file except in compliance with the License.
5
# You may obtain a copy of the License at
6
#
7
#     http://www.apache.org/licenses/LICENSE-2.0
8
#
9
# Unless required by applicable law or agreed to in writing, software
10
# distributed under the License is distributed on an "AS IS" BASIS,
11
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
# See the License for the specific language governing permissions and
13
# limitations under the License.
14
15
"""
16
.. module:: plugin
17
   :platform: Unix
18
   :synopsis: Base class for all plugins used by Savu
19
20
.. moduleauthor:: Mark Basham <[email protected]>
21
22
"""
23
24
import copy
25
import logging
26
import numpy as np
27
28
import savu.plugins.utils as pu
29
from savu.plugins.plugin_datasets import PluginDatasets
30
from savu.plugins.stats.statistics import Statistics
31
32
33
class Plugin(PluginDatasets):
34
35
    def __init__(self, name="Plugin"):
36
        super(Plugin, self).__init__(name)
37
        self.name = name
38
        self.chunk = False
39
        self.slice_list = None
40
        self.global_index = None
41
        self.pcount = 0
42
        self.exp = None
43
        self.check = False
44
        self.fixed_length = True
45
        self.parameters = {}
46
        self.tools = self._set_plugin_tools()
47
48
    def set_parameters(self, params):
49
        self.parameters = params
50
51
    def initialise(self, params, exp, check=False):
52
        self.check = check
53
        self.exp = exp
54
        self.get_plugin_tools().initialise(params)
55
        self._main_setup()
56
57
    def _main_setup(self):
58
        """ Performs all the required plugin setup.
59
60
        It sets the experiment, then the parameters and replaces the
61
        in/out_dataset strings in ``self.parameters`` with the relevant data
62
        objects. It then creates PluginData objects for each of these datasets.
63
        """
64
        self._set_plugin_datasets()
65
        self._reset_process_frames_counter()
66
        self.setup()
67
        self.set_filter_padding(*(self.get_plugin_datasets()))
68
        self._finalise_plugin_datasets()
69
        self._finalise_datasets()
70
        self.stats_obj = Statistics(self)
71
72
    def _reset_process_frames_counter(self):
73
        self.pcount = 0
74
75
    def get_process_frames_counter(self):
76
        return self.pcount
77
78
    def set_filter_padding(self, in_data, out_data):
79
        """
80
        Should be overridden to define how wide the frame should be for each
81
        input data set
82
        """
83
        return {}
84
85
    def setup(self):
86
        """
87
        This method is first to be called after the plugin has been created.
88
        It determines input/output datasets and plugin specific dataset
89
        information such as the pattern (e.g. sinogram/projection).
90
        """
91
        logging.error("set_up needs to be implemented")
92
        raise NotImplementedError("setup needs to be implemented")
93
94
    def get_plugin_tools(self):
95
        return self.tools
96
97
    def _set_plugin_tools(self):
98
        plugin_tools_id = self.__class__.__module__ + '_tools'
99
        tool_class = pu.get_tools_class(plugin_tools_id, self)
100
        return tool_class
101
102
    def delete_parameter_entry(self, param):
103
        if param in list(self.parameters.keys()):
104
            del self.parameters[param]
105
106
    def get_parameters(self, name):
107
        """ Return a plugin parameter
108
109
        :params str name: parameter name (dictionary key)
110
        :returns: the associated value in ``self.parameters``
111
        :rtype: dict value
112
        """
113
        return self.parameters[name]
114
115
    def base_pre_process(self):
116
        """ This method is called after the plugin has been created by the
117
        pipeline framework as a pre-processing step.
118
        """
119
        pass
120
121
    def pre_process(self):
122
        """ This method is called immediately after base_pre_process(). """
123
        pass
124
125
    def base_process_frames_before(self, data):
126
        """ This method is called before each call to process frames """
127
        return data
128
129
    def base_process_frames_after(self, data):
130
        """ This method is called directly after each call to process frames \
131
        and before returning the data to file."""
132
        return data
133
134
    def plugin_process_frames(self, data):
135
        frames = self.base_process_frames_after(self.process_frames(
136
                self.base_process_frames_before(data)))
137
        if self.stats_obj.calc_stats:
138
            self.stats_obj.set_slice_stats(frames)
139
        self.pcount += 1
140
        return frames
141
142
    def process_frames(self, data):
143
        """
144
        This method is called after the plugin has been created by the
145
        pipeline framework and forms the main processing step
146
147
        :param data: A list of numpy arrays for each input dataset.
148
        :type data: list(np.array)
149
        """
150
151
        logging.error("process frames needs to be implemented")
152
        raise NotImplementedError("process needs to be implemented")
153
154
    def post_process(self):
155
        """
156
        This method is called after the process function in the pipeline
157
        framework as a post-processing step. All processes will have finished
158
        performing the main processing at this stage.
159
160
        :param exp: An experiment object, holding input and output datasets
161
        :type exp: experiment class instance
162
        """
163
        pass
164
165
    def base_post_process(self):
166
        """ This method is called immediately after post_process(). """
167
        if self.stats_obj.calc_stats:
168
            self.stats_obj.set_volume_stats()
169
        pass
170
171
    def set_preview(self, data, params):
172
        if not params:
173
            return True
174
        preview = data.get_preview()
175
        orig_indices = preview.get_starts_stops_steps()
176
        nDims = len(orig_indices[0])
177
        no_preview = [[0]*nDims, data.get_shape(), [1]*nDims, [1]*nDims]
178
179
        # Set previewing params if previewing has not already been applied to
180
        # the dataset.
181
        if no_preview == orig_indices:
182
            data.get_preview().revert_shape = data.get_shape()
183
            data.get_preview().set_preview(params)
184
            return True
185
        return False
186
187
    def _clean_up(self):
188
        """ Perform necessary plugin clean up after the plugin has completed.
189
        """
190
        self._clone_datasets()
191
        self.__copy_meta_data()
192
        self.__set_previous_patterns()
193
        self.__clean_up_plugin_data()
194
195
    def __copy_meta_data(self):
196
        """
197
        Copy all metadata from input datasets to output datasets, except axis
198
        data that is no longer valid.
199
        """
200
        remove_keys = self.__remove_axis_data()
201
        in_meta_data, out_meta_data = self.get()
202
        copy_dict = {}
203
        for mData in in_meta_data:
204
            temp = copy.deepcopy(mData.get_dictionary())
205
            copy_dict.update(temp)
206
207
        for i in range(len(out_meta_data)):
208
            temp = copy_dict.copy()
209
            for key in remove_keys[i]:
210
                if temp.get(key, None) is not None:
211
                    del temp[key]
212
            temp.update(out_meta_data[i].get_dictionary())
213
            out_meta_data[i]._set_dictionary(temp)
214
215
    def __set_previous_patterns(self):
216
        for data in self.get_out_datasets():
217
            data._set_previous_pattern(
218
                copy.deepcopy(data._get_plugin_data().get_pattern()))
219
220
    def __remove_axis_data(self):
221
        """
222
        Returns a list of meta_data entries corresponding to axis labels that
223
        are not copied over to the output datasets
224
        """
225
        in_datasets, out_datasets = self.get_datasets()
226
        all_in_labels = []
227
        for data in in_datasets:
228
            axis_keys = data.get_axis_label_keys()
229
            all_in_labels = all_in_labels + axis_keys
230
231
        remove_keys = []
232
        for data in out_datasets:
233
            axis_keys = data.get_axis_label_keys()
234
            remove_keys.append(set(all_in_labels).difference(set(axis_keys)))
235
236
        return remove_keys
237
238
    def __clean_up_plugin_data(self):
239
        """ Remove pluginData object encapsulated in a dataset after plugin
240
        completion.
241
        """
242
        in_data, out_data = self.get_datasets()
243
        data_object_list = in_data + out_data
244
        for data in data_object_list:
245
            data._clear_plugin_data()
246
247
    def _revert_preview(self, in_data):
248
        """ Revert dataset back to original shape if previewing was used in a
249
        plugin to reduce the data shape but the original data shape should be
250
        used thereafter. Remove previewing if it was added in the plugin.
251
        """
252
        for data in in_data:
253
            if data.get_preview().revert_shape:
254
                data.get_preview()._unset_preview()
255
256
    def set_global_frame_index(self, frame_idx):
257
        self.global_index = frame_idx
258
259
    def get_global_frame_index(self):
260
        """ Get the global frame index. """
261
        return self.global_index
262
263
    def set_current_slice_list(self, sl):
264
        self.slice_list = sl
265
266
    def get_current_slice_list(self):
267
        """ Get the slice list of the current frame being processed. """
268
        return self.slice_list
269
270
    def get_slice_dir_reps(self, nData):
271
        """ Return the periodicity of the main slice direction.
272
273
        :params int nData: The number of the dataset in the list.
274
        """
275
        slice_dir = \
276
            self.get_plugin_in_datasets()[nData].get_slice_directions()[0]
277
        sl = [sl[slice_dir] for sl in self.slice_list]
278
        reps = [i for i in range(len(sl)) if sl[i] == sl[0]]
279
        return np.diff(reps)[0] if len(reps) > 1 else 1
280
281
    def nInput_datasets(self):
282
        """
283
        The number of datasets required as input to the plugin
284
285
        :returns:  Number of input datasets
286
287
        """
288
        return 1
289
290
    def nOutput_datasets(self):
291
        """
292
        The number of datasets created by the plugin
293
294
        :returns:  Number of output datasets
295
296
        """
297
        return 1
298
299
    def nClone_datasets(self):
300
        """ The number of output datasets that have an clone - i.e. they take\
301
        it in turns to be used as output in an iterative plugin.
302
        """
303
        return 0
304
305
    def nFrames(self):
306
        """ The number of frames to process during each call to process_frames.
307
        """
308
        return 'single'
309
310
    def final_parameter_updates(self):
311
        """ An opportunity to update the parameters after they have been set.
312
        """
313
        pass
314
315
    def executive_summary(self):
316
        """ Provide a summary to the user for the result of the plugin.
317
318
        e.g.
319
         - Warning, the sample may have shifted during data collection
320
         - Filter operated normally
321
322
        :returns:  A list of string summaries
323
        """
324
        return ["Nothing to Report"]
325