Test Failed
Pull Request — master (#878)
by Yousef
04:34
created

savu.plugins.plugin   D

Complexity

Total Complexity 58

Size/Duplication

Total Lines 335
Duplicated Lines 0 %

Importance

Changes 0
Metric Value
eloc 166
dl 0
loc 335
rs 4.5599
c 0
b 0
f 0
wmc 58

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_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.get_global_frame_index() 0 3 1
A Plugin.get_current_slice_list() 0 3 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.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._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
A Plugin.set_preview() 0 15 3
B Plugin.__copy_meta_data() 0 21 6
A Plugin.post_process() 0 10 1
A Plugin.process_frames() 0 11 1
A Plugin.base_post_process() 0 9 4
A Plugin.plugin_process_frames() 0 9 3
A Plugin._clean_up() 0 7 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 and self.stats_obj._stats_flag:
142
            self.stats_obj.set_slice_stats(frames, data_copy)
143
        self.pcount += 1
144
        return frames
145
146
    def process_frames(self, data):
147
        """
148
        This method is called after the plugin has been created by the
149
        pipeline framework and forms the main processing step
150
151
        :param data: A list of numpy arrays for each input dataset.
152
        :type data: list(np.array)
153
        """
154
155
        logging.error("process frames needs to be implemented")
156
        raise NotImplementedError("process needs to be implemented")
157
158
    def post_process(self):
159
        """
160
        This method is called after the process function in the pipeline
161
        framework as a post-processing step. All processes will have finished
162
        performing the main processing at this stage.
163
164
        :param exp: An experiment object, holding input and output datasets
165
        :type exp: experiment class instance
166
        """
167
        pass
168
169
    def base_post_process(self):
170
        """ This method is called immediately after post_process(). """
171
        if self.stats_obj.calc_stats and self.stats_obj._stats_flag:
172
            if not self.stats_obj._already_called:
173
                self.stats_obj.set_volume_stats()
174
            self.stats_obj._already_called = False
175
        #else:
176
        #    self.stats_obj._delete_stats_metadata(self)
177
        pass
178
179
    def set_preview(self, data, params):
180
        if not params:
181
            return True
182
        preview = data.get_preview()
183
        orig_indices = preview.get_starts_stops_steps()
184
        nDims = len(orig_indices[0])
185
        no_preview = [[0]*nDims, data.get_shape(), [1]*nDims, [1]*nDims]
186
187
        # Set previewing params if previewing has not already been applied to
188
        # the dataset.
189
        if no_preview == orig_indices:
190
            data.get_preview().revert_shape = data.get_shape()
191
            data.get_preview().set_preview(params)
192
            return True
193
        return False
194
195
    def _clean_up(self):
196
        """ Perform necessary plugin clean up after the plugin has completed.
197
        """
198
        self._clone_datasets()
199
        self.__copy_meta_data()
200
        self.__set_previous_patterns()
201
        self.__clean_up_plugin_data()
202
203
    def __copy_meta_data(self):
204
        """
205
        Copy all metadata from input datasets to output datasets, except axis
206
        data and statistics that is no longer valid.
207
        """
208
        remove_keys = self.__remove_axis_data()
209
        for i in range(len(remove_keys)):
210
            remove_keys[i].add("stats")
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