Test Failed
Pull Request — master (#878)
by
unknown
04:01
created

savu.plugins.plugin   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 168
dl 0
loc 335
rs 6
c 0
b 0
f 0
wmc 55

38 Methods

Rating   Name   Duplication   Size   Complexity  
A Plugin.set_parameters() 0 2 1
A Plugin.initialise() 0 5 1
A Plugin.__init__() 0 12 1
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_plugin_tools() 0 4 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 7 3
A Plugin.plugin_process_frames() 0 13 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 15 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.stats_obj = Statistics()
67
        self.setup()
68
        self.stats_obj.setup(self)
69
        self.set_filter_padding(*(self.get_plugin_datasets()))
70
        self._finalise_plugin_datasets()
71
        self._finalise_datasets()
72
73
74
    def _reset_process_frames_counter(self):
75
        self.pcount = 0
76
77
    def get_process_frames_counter(self):
78
        return self.pcount
79
80
    def set_filter_padding(self, in_data, out_data):
81
        """
82
        Should be overridden to define how wide the frame should be for each
83
        input data set
84
        """
85
        return {}
86
87
    def setup(self):
88
        """
89
        This method is first to be called after the plugin has been created.
90
        It determines input/output datasets and plugin specific dataset
91
        information such as the pattern (e.g. sinogram/projection).
92
        """
93
        logging.error("set_up needs to be implemented")
94
        raise NotImplementedError("setup needs to be implemented")
95
96
    def get_plugin_tools(self):
97
        return self.tools
98
99
    def _set_plugin_tools(self):
100
        plugin_tools_id = self.__class__.__module__ + '_tools'
101
        tool_class = pu.get_tools_class(plugin_tools_id, self)
102
        return tool_class
103
104
    def delete_parameter_entry(self, param):
105
        if param in list(self.parameters.keys()):
106
            del self.parameters[param]
107
108
    def get_parameters(self, name):
109
        """ Return a plugin parameter
110
111
        :params str name: parameter name (dictionary key)
112
        :returns: the associated value in ``self.parameters``
113
        :rtype: dict value
114
        """
115
        return self.parameters[name]
116
117
    def base_pre_process(self):
118
        """ This method is called after the plugin has been created by the
119
        pipeline framework as a pre-processing step.
120
        """
121
        pass
122
123
    def pre_process(self):
124
        """ This method is called immediately after base_pre_process(). """
125
        pass
126
127
    def base_process_frames_before(self, data):
128
        """ This method is called before each call to process frames """
129
        return data
130
131
    def base_process_frames_after(self, data):
132
        """ This method is called directly after each call to process frames \
133
        and before returning the data to file."""
134
        return data
135
136
    def plugin_process_frames(self, data):
137
        data_copy = data.copy()  # is it ok to copy every frame like this? Enough memory?
138
        frames = self.base_process_frames_after(self.process_frames(
139
                self.base_process_frames_before(data)))
140
141
        if self.stats_obj.calc_stats:
142
            slice_stats_before = self.stats_obj.calc_slice_stats(data_copy)
143
            slice_stats_after = self.stats_obj.calc_slice_stats(frames, data_copy)
144
            self.stats_obj.set_slice_stats(slice_stats_after)
145
            stats_residuals = self.stats_obj.calc_stats_residuals(slice_stats_before, slice_stats_after)
146
            self.stats_obj.set_stats_residuals(stats_residuals)
147
        self.pcount += 1
148
        return frames
149
150
    def process_frames(self, data):
151
        """
152
        This method is called after the plugin has been created by the
153
        pipeline framework and forms the main processing step
154
155
        :param data: A list of numpy arrays for each input dataset.
156
        :type data: list(np.array)
157
        """
158
159
        logging.error("process frames needs to be implemented")
160
        raise NotImplementedError("process needs to be implemented")
161
162
    def post_process(self):
163
        """
164
        This method is called after the process function in the pipeline
165
        framework as a post-processing step. All processes will have finished
166
        performing the main processing at this stage.
167
168
        :param exp: An experiment object, holding input and output datasets
169
        :type exp: experiment class instance
170
        """
171
        pass
172
173
    def base_post_process(self):
174
        """ This method is called immediately after post_process(). """
175
        if self.stats_obj.calc_stats:
176
            if not self.stats_obj._already_called:
177
                self.stats_obj.set_volume_stats()
178
            self.stats_obj._already_called = False
179
        pass
180
181
    def set_preview(self, data, params):
182
        if not params:
183
            return True
184
        preview = data.get_preview()
185
        orig_indices = preview.get_starts_stops_steps()
186
        nDims = len(orig_indices[0])
187
        no_preview = [[0]*nDims, data.get_shape(), [1]*nDims, [1]*nDims]
188
189
        # Set previewing params if previewing has not already been applied to
190
        # the dataset.
191
        if no_preview == orig_indices:
192
            data.get_preview().revert_shape = data.get_shape()
193
            data.get_preview().set_preview(params)
194
            return True
195
        return False
196
197
    def _clean_up(self):
198
        """ Perform necessary plugin clean up after the plugin has completed.
199
        """
200
        self._clone_datasets()
201
        self.__copy_meta_data()
202
        self.__set_previous_patterns()
203
        self.__clean_up_plugin_data()
204
205
    def __copy_meta_data(self):
206
        """
207
        Copy all metadata from input datasets to output datasets, except axis
208
        data that is no longer valid.
209
        """
210
        remove_keys = self.__remove_axis_data()
211
        in_meta_data, out_meta_data = self.get()
212
        copy_dict = {}
213
        for mData in in_meta_data:
214
            temp = copy.deepcopy(mData.get_dictionary())
215
            copy_dict.update(temp)
216
217
        for i in range(len(out_meta_data)):
218
            temp = copy_dict.copy()
219
            for key in remove_keys[i]:
220
                if temp.get(key, None) is not None:
221
                    del temp[key]
222
            temp.update(out_meta_data[i].get_dictionary())
223
            out_meta_data[i]._set_dictionary(temp)
224
225
    def __set_previous_patterns(self):
226
        for data in self.get_out_datasets():
227
            data._set_previous_pattern(
228
                copy.deepcopy(data._get_plugin_data().get_pattern()))
229
230
    def __remove_axis_data(self):
231
        """
232
        Returns a list of meta_data entries corresponding to axis labels that
233
        are not copied over to the output datasets
234
        """
235
        in_datasets, out_datasets = self.get_datasets()
236
        all_in_labels = []
237
        for data in in_datasets:
238
            axis_keys = data.get_axis_label_keys()
239
            all_in_labels = all_in_labels + axis_keys
240
241
        remove_keys = []
242
        for data in out_datasets:
243
            axis_keys = data.get_axis_label_keys()
244
            remove_keys.append(set(all_in_labels).difference(set(axis_keys)))
245
246
        return remove_keys
247
248
    def __clean_up_plugin_data(self):
249
        """ Remove pluginData object encapsulated in a dataset after plugin
250
        completion.
251
        """
252
        in_data, out_data = self.get_datasets()
253
        data_object_list = in_data + out_data
254
        for data in data_object_list:
255
            data._clear_plugin_data()
256
257
    def _revert_preview(self, in_data):
258
        """ Revert dataset back to original shape if previewing was used in a
259
        plugin to reduce the data shape but the original data shape should be
260
        used thereafter. Remove previewing if it was added in the plugin.
261
        """
262
        for data in in_data:
263
            if data.get_preview().revert_shape:
264
                data.get_preview()._unset_preview()
265
266
    def set_global_frame_index(self, frame_idx):
267
        self.global_index = frame_idx
268
269
    def get_global_frame_index(self):
270
        """ Get the global frame index. """
271
        return self.global_index
272
273
    def set_current_slice_list(self, sl):
274
        self.slice_list = sl
275
276
    def get_current_slice_list(self):
277
        """ Get the slice list of the current frame being processed. """
278
        return self.slice_list
279
280
    def get_slice_dir_reps(self, nData):
281
        """ Return the periodicity of the main slice direction.
282
283
        :params int nData: The number of the dataset in the list.
284
        """
285
        slice_dir = \
286
            self.get_plugin_in_datasets()[nData].get_slice_directions()[0]
287
        sl = [sl[slice_dir] for sl in self.slice_list]
288
        reps = [i for i in range(len(sl)) if sl[i] == sl[0]]
289
        return np.diff(reps)[0] if len(reps) > 1 else 1
290
291
    def nInput_datasets(self):
292
        """
293
        The number of datasets required as input to the plugin
294
295
        :returns:  Number of input datasets
296
297
        """
298
        return 1
299
300
    def nOutput_datasets(self):
301
        """
302
        The number of datasets created by the plugin
303
304
        :returns:  Number of output datasets
305
306
        """
307
        return 1
308
309
    def nClone_datasets(self):
310
        """ The number of output datasets that have an clone - i.e. they take\
311
        it in turns to be used as output in an iterative plugin.
312
        """
313
        return 0
314
315
    def nFrames(self):
316
        """ The number of frames to process during each call to process_frames.
317
        """
318
        return 'single'
319
320
    def final_parameter_updates(self):
321
        """ An opportunity to update the parameters after they have been set.
322
        """
323
        pass
324
325
    def executive_summary(self):
326
        """ Provide a summary to the user for the result of the plugin.
327
328
        e.g.
329
         - Warning, the sample may have shifted during data collection
330
         - Filter operated normally
331
332
        :returns:  A list of string summaries
333
        """
334
        return ["Nothing to Report"]
335