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

savu.plugins.plugin   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 332
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 164
dl 0
loc 332
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.setup() 0 8 1
A Plugin._set_plugin_tools() 0 4 1
A Plugin.base_pre_process() 0 5 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.pre_process() 0 3 1
A Plugin.delete_parameter_entry() 0 3 2
A Plugin._main_setup() 0 15 1
A Plugin.nFrames() 0 4 1
A Plugin.executive_summary() 0 10 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.post_process() 0 10 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.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 10 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

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
            self.stats_obj.set_slice_stats(frames, data_copy)
143
144
        self.pcount += 1
145
        return frames
146
147
    def process_frames(self, data):
148
        """
149
        This method is called after the plugin has been created by the
150
        pipeline framework and forms the main processing step
151
152
        :param data: A list of numpy arrays for each input dataset.
153
        :type data: list(np.array)
154
        """
155
156
        logging.error("process frames needs to be implemented")
157
        raise NotImplementedError("process needs to be implemented")
158
159
    def post_process(self):
160
        """
161
        This method is called after the process function in the pipeline
162
        framework as a post-processing step. All processes will have finished
163
        performing the main processing at this stage.
164
165
        :param exp: An experiment object, holding input and output datasets
166
        :type exp: experiment class instance
167
        """
168
        pass
169
170
    def base_post_process(self):
171
        """ This method is called immediately after post_process(). """
172
        if self.stats_obj.calc_stats:
173
            if not self.stats_obj._already_called:
174
                self.stats_obj.set_volume_stats()
175
            self.stats_obj._already_called = False
176
        pass
177
178
    def set_preview(self, data, params):
179
        if not params:
180
            return True
181
        preview = data.get_preview()
182
        orig_indices = preview.get_starts_stops_steps()
183
        nDims = len(orig_indices[0])
184
        no_preview = [[0]*nDims, data.get_shape(), [1]*nDims, [1]*nDims]
185
186
        # Set previewing params if previewing has not already been applied to
187
        # the dataset.
188
        if no_preview == orig_indices:
189
            data.get_preview().revert_shape = data.get_shape()
190
            data.get_preview().set_preview(params)
191
            return True
192
        return False
193
194
    def _clean_up(self):
195
        """ Perform necessary plugin clean up after the plugin has completed.
196
        """
197
        self._clone_datasets()
198
        self.__copy_meta_data()
199
        self.__set_previous_patterns()
200
        self.__clean_up_plugin_data()
201
202
    def __copy_meta_data(self):
203
        """
204
        Copy all metadata from input datasets to output datasets, except axis
205
        data that is no longer valid.
206
        """
207
        remove_keys = self.__remove_axis_data()
208
        in_meta_data, out_meta_data = self.get()
209
        copy_dict = {}
210
        for mData in in_meta_data:
211
            temp = copy.deepcopy(mData.get_dictionary())
212
            copy_dict.update(temp)
213
214
        for i in range(len(out_meta_data)):
215
            temp = copy_dict.copy()
216
            for key in remove_keys[i]:
217
                if temp.get(key, None) is not None:
218
                    del temp[key]
219
            temp.update(out_meta_data[i].get_dictionary())
220
            out_meta_data[i]._set_dictionary(temp)
221
222
    def __set_previous_patterns(self):
223
        for data in self.get_out_datasets():
224
            data._set_previous_pattern(
225
                copy.deepcopy(data._get_plugin_data().get_pattern()))
226
227
    def __remove_axis_data(self):
228
        """
229
        Returns a list of meta_data entries corresponding to axis labels that
230
        are not copied over to the output datasets
231
        """
232
        in_datasets, out_datasets = self.get_datasets()
233
        all_in_labels = []
234
        for data in in_datasets:
235
            axis_keys = data.get_axis_label_keys()
236
            all_in_labels = all_in_labels + axis_keys
237
238
        remove_keys = []
239
        for data in out_datasets:
240
            axis_keys = data.get_axis_label_keys()
241
            remove_keys.append(set(all_in_labels).difference(set(axis_keys)))
242
243
        return remove_keys
244
245
    def __clean_up_plugin_data(self):
246
        """ Remove pluginData object encapsulated in a dataset after plugin
247
        completion.
248
        """
249
        in_data, out_data = self.get_datasets()
250
        data_object_list = in_data + out_data
251
        for data in data_object_list:
252
            data._clear_plugin_data()
253
254
    def _revert_preview(self, in_data):
255
        """ Revert dataset back to original shape if previewing was used in a
256
        plugin to reduce the data shape but the original data shape should be
257
        used thereafter. Remove previewing if it was added in the plugin.
258
        """
259
        for data in in_data:
260
            if data.get_preview().revert_shape:
261
                data.get_preview()._unset_preview()
262
263
    def set_global_frame_index(self, frame_idx):
264
        self.global_index = frame_idx
265
266
    def get_global_frame_index(self):
267
        """ Get the global frame index. """
268
        return self.global_index
269
270
    def set_current_slice_list(self, sl):
271
        self.slice_list = sl
272
273
    def get_current_slice_list(self):
274
        """ Get the slice list of the current frame being processed. """
275
        return self.slice_list
276
277
    def get_slice_dir_reps(self, nData):
278
        """ Return the periodicity of the main slice direction.
279
280
        :params int nData: The number of the dataset in the list.
281
        """
282
        slice_dir = \
283
            self.get_plugin_in_datasets()[nData].get_slice_directions()[0]
284
        sl = [sl[slice_dir] for sl in self.slice_list]
285
        reps = [i for i in range(len(sl)) if sl[i] == sl[0]]
286
        return np.diff(reps)[0] if len(reps) > 1 else 1
287
288
    def nInput_datasets(self):
289
        """
290
        The number of datasets required as input to the plugin
291
292
        :returns:  Number of input datasets
293
294
        """
295
        return 1
296
297
    def nOutput_datasets(self):
298
        """
299
        The number of datasets created by the plugin
300
301
        :returns:  Number of output datasets
302
303
        """
304
        return 1
305
306
    def nClone_datasets(self):
307
        """ The number of output datasets that have an clone - i.e. they take\
308
        it in turns to be used as output in an iterative plugin.
309
        """
310
        return 0
311
312
    def nFrames(self):
313
        """ The number of frames to process during each call to process_frames.
314
        """
315
        return 'single'
316
317
    def final_parameter_updates(self):
318
        """ An opportunity to update the parameters after they have been set.
319
        """
320
        pass
321
322
    def executive_summary(self):
323
        """ Provide a summary to the user for the result of the plugin.
324
325
        e.g.
326
         - Warning, the sample may have shifted during data collection
327
         - Filter operated normally
328
329
        :returns:  A list of string summaries
330
        """
331
        return ["Nothing to Report"]
332