Completed
Push — master ( a63a82...61f52c )
by
unknown
01:32
created

pcnn_network.__init__()   F

Complexity

Conditions 9

Size

Total Lines 47

Duplication

Lines 0
Ratio 0 %
Metric Value
cc 9
dl 0
loc 47
rs 3.3333
1
"""!
2
3
@brief Neural Network: Pulse Coupled Neural Network
4
@details Based on book description:
5
         - T.Lindblad, J.M.Kinser. Image Processing Using Pulse-Coupled Neural Networks (2nd edition). 2005.
6
7
@authors Andrei Novikov ([email protected])
8
@date 2014-2016
9
@copyright GNU Public License
10
11
@cond GNU_PUBLIC_LICENSE
12
    PyClustering is free software: you can redistribute it and/or modify
13
    it under the terms of the GNU General Public License as published by
14
    the Free Software Foundation, either version 3 of the License, or
15
    (at your option) any later version.
16
    
17
    PyClustering is distributed in the hope that it will be useful,
18
    but WITHOUT ANY WARRANTY; without even the implied warranty of
19
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20
    GNU General Public License for more details.
21
    
22
    You should have received a copy of the GNU General Public License
23
    along with this program.  If not, see <http://www.gnu.org/licenses/>.
24
@endcond
25
26
"""
27
28
import matplotlib.pyplot as plt;
0 ignored issues
show
Configuration introduced by
The import matplotlib.pyplot could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
29
import matplotlib.animation as animation;
0 ignored issues
show
Configuration introduced by
The import matplotlib.animation could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
30
31
import random;
32
import numpy;
0 ignored issues
show
Configuration introduced by
The import numpy could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
33
34
from PIL import Image;
0 ignored issues
show
Configuration introduced by
The import PIL could not be resolved.

This can be caused by one of the following:

1. Missing Dependencies

This error could indicate a configuration issue of Pylint. Make sure that your libraries are available by adding the necessary commands.

# .scrutinizer.yml
before_commands:
    - sudo pip install abc # Python2
    - sudo pip3 install abc # Python3
Tip: We are currently not using virtualenv to run pylint, when installing your modules make sure to use the command for the correct version.

2. Missing __init__.py files

This error could also result from missing __init__.py files in your module folders. Make sure that you place one file in each sub-folder.

Loading history...
35
36
from pyclustering.nnet import *;
0 ignored issues
show
Unused Code introduced by
IntEnum was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
solve_type was imported with wildcard, but is not used.
Loading history...
Unused Code introduced by
initial_type was imported with wildcard, but is not used.
Loading history...
37
import pyclustering.core.pcnn_wrapper as wrapper;
38
39
from pyclustering.utils import draw_dynamics;
40
41
42
class pcnn_parameters:
43
    """!
44
    @brief Parameters for pulse coupled neural network.
45
    
46
    """
47
    
48
    ## Multiplier for the feeding compartment at the current step.
49
    VF = 1.0;
50
    
51
    ## Multiplier for the linking compartment at the current step.  
52
    VL = 1.0;
53
    
54
    ## Multiplier for the threshold at the current step.    
55
    VT = 10.0;
56
    
57
    
58
    ## Multiplier for the feeding compartment at the previous step.    
59
    AF = 0.1;
60
    
61
    ## Multiplier for the linking compartment at the previous step.
62
    AL = 0.1;
63
    
64
    ## Multiplier for the threshold at the previous step.
65
    AT = 0.5;
66
    
67
    
68
    ## Synaptic weight - neighbours influence on linking compartment
69
    W = 1.0;
70
    
71
    ## Synaptic weight - neighbours influence on feeding compartment.
72
    M = 1.0;
73
    
74
    
75
    ## Linking strength in the network.
76
    B = 0.1;
77
    
78
    ## Enable/disable Fast-Linking mode. Fast linking helps to overcome some of the effects of time quantisation. This process allows the linking wave to progress a lot faster than the feeding wave.
79
    FAST_LINKING = False;
80
    
81
    
82
class pcnn_dynamic:
83
    """!
84
    @brief Represents output dynamic of PCNN.
85
    
86
    """
87
    __dynamic = None;
88
    __ccore_pcnn_dynamic_pointer = None;
89
    
90
    __OUTPUT_TRUE = 1;    # fire value for oscillators.
91
    __OUTPUT_FALSE = 0;   # rest value for oscillators.
92
        
93
    @property
94
    def output(self):
95
        """!
96
        @brief (list) Returns outputs of oscillator during simulation.
97
        
98
        """
99
        if (self.__ccore_pcnn_dynamic_pointer is not None):
100
            return wrapper.pcnn_dynamic_get_output(self.__ccore_pcnn_dynamic_pointer);
101
            
102
        return self.__dynamic;
103
    
104
    
105
    @property
106
    def time(self):
107
        """!
108
        @brief (list) Returns sampling times when dynamic is measured during simulation.
109
        
110
        """
111
        if (self.__ccore_pcnn_dynamic_pointer is not None):
112
            return wrapper.pcnn_dynamic_get_time(self.__ccore_pcnn_dynamic_pointer);
113
        
114
        return list(range(len(self)));
115
    
116
    
117
    def __init__(self, dynamic, ccore = None):
118
        """!
119
        @brief Constructor of PCNN dynamic.
120
        
121
        @param[in] dynamic (list): Dynamic of oscillators on each step of simulation. If ccore pointer is specified than it can be ignored.
122
        @param[in] ccore (ctypes.pointer): Pointer to CCORE pcnn_dynamic instance in memory.
123
        
124
        """
125
        self.__dynamic = dynamic;
126
        self.__ccore_pcnn_dynamic_pointer = ccore;
127
    
128
    
129
    def __del__(self):
130
        """!
131
        @brief Default destructor of PCNN dynamic.
132
        
133
        """
134
        if (self.__ccore_pcnn_dynamic_pointer is not None):
135
            wrapper.pcnn_dynamic_destroy(self.__ccore_pcnn_dynamic_pointer);
136
    
137
    
138
    def __len__(self):
139
        """!
140
        @brief (uint) Returns number of simulation steps that are stored in dynamic.
141
        
142
        """
143
        if (self.__ccore_pcnn_dynamic_pointer is not None):
144
            return wrapper.pcnn_dynamic_get_size(self.__ccore_pcnn_dynamic_pointer);
145
        
146
        return len(self.__dynamic);
147
    
148
    
149 View Code Duplication
    def allocate_sync_ensembles(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
150
        """!
151
        @brief Allocate clusters in line with ensembles of synchronous oscillators where each
152
               synchronous ensemble corresponds to only one cluster.
153
        
154
        @return (list) Grours (lists) of indexes of synchronous oscillators. 
155
                For example, [ [index_osc1, index_osc3], [index_osc2], [index_osc4, index_osc5] ].
156
                
157
        """
158
        
159
        if (self.__ccore_pcnn_dynamic_pointer is not None):
160
            return wrapper.pcnn_dynamic_allocate_sync_ensembles(self.__ccore_pcnn_dynamic_pointer);
161
        
162
        sync_ensembles = [];
163
        traverse_oscillators = set();
164
        
165
        number_oscillators = len(self.__dynamic[0]);
166
        
167
        for t in range(len(self.__dynamic) - 1, 0, -1):
168
            sync_ensemble = [];
169
            for i in range(number_oscillators):
170
                if (self.__dynamic[t][i] == self.__OUTPUT_TRUE):
171
                    if (i not in traverse_oscillators):
172
                        sync_ensemble.append(i);
173
                        traverse_oscillators.add(i);
174
            
175
            if (sync_ensemble != []):
176
                sync_ensembles.append(sync_ensemble);
177
        
178
        return sync_ensembles;
179
180
181 View Code Duplication
    def allocate_spike_ensembles(self):
0 ignored issues
show
Duplication introduced by
This code seems to be duplicated in your project.
Loading history...
182
        """!
183
        @brief Analyses output dynamic of network and allocates spikes on each iteration as a list of indexes of oscillators.
184
        @details Each allocated spike ensemble represents list of indexes of oscillators whose output is active.
185
        
186
        @return (list) Spike ensembles of oscillators.
187
        
188
        """
189
        
190
        if (self.__ccore_pcnn_dynamic_pointer is not None):
191
            return wrapper.pcnn_dynamic_allocate_spike_ensembles(self.__ccore_pcnn_dynamic_pointer);
192
        
193
        spike_ensembles = [];
194
        number_oscillators = len(self.__dynamic[0]);
195
        
196
        for t in range(len(self.__dynamic)):
197
            spike_ensemble = [];
198
            
199
            for index in range(number_oscillators):
200
                if (self.__dynamic[t][index] == self.__OUTPUT_TRUE):
201
                    spike_ensemble.append(index);
202
            
203
            if (len(spike_ensemble) > 0):
204
                spike_ensembles.append(spike_ensemble);
205
        
206
        return spike_ensembles;
207
    
208
    
209
    def allocate_time_signal(self):
210
        """!
211
        @brief Analyses output dynamic and calculates time signal (signal vector information) of network output.
212
           
213
        @return (list) Time signal of network output.
214
        
215
        """
216
        
217
        if (self.__ccore_pcnn_dynamic_pointer is not None):
218
            return wrapper.pcnn_dynamic_allocate_time_signal(self.__ccore_pcnn_dynamic_pointer);
219
        
220
        signal_vector_information = [];
221
        for t in range(0, len(self.__dynamic)):
222
            signal_vector_information.append(sum(self.__dynamic[t]));
223
        
224
        return signal_vector_information;
225
226
227
class pcnn_visualizer:
228
    """!
229
    @brief Visualizer of output dynamic of pulse-coupled neural network (PCNN).
230
    
231
    """
232
    
233
    @staticmethod
234
    def show_time_signal(pcnn_output_dynamic):
235
        """!
236
        @brief Shows time signal (signal vector information) using network dynamic during simulation.
237
        
238
        @param[in] pcnn_output_dynamic (pcnn_dynamic): Output dynamic of the pulse-coupled neural network.
239
        
240
        """
241
        
242
        time_signal = pcnn_output_dynamic.allocate_time_signal();
243
        time_axis = range(len(time_signal));
244
        
245
        plt.subplot(1, 1, 1);
246
        plt.plot(time_axis, time_signal, '-');
247
        plt.ylabel("G (time signal)");
248
        plt.xlabel("t (iteration)");
249
        plt.grid(True);
250
        
251
        plt.show();
252
        
253
    @staticmethod
254
    def show_output_dynamic(pcnn_output_dynamic, separate_representation = False):
255
        """!
256
        @brief Shows output dynamic (output of each oscillator) during simulation.
257
        
258
        @param[in] pcnn_output_dynamic (pcnn_dynamic): Output dynamic of the pulse-coupled neural network.
259
        @param[in] separate_representation (list): Consists of lists of oscillators where each such list consists of oscillator indexes that will be shown on separated stage.
260
        
261
        """
262
        
263
        draw_dynamics(pcnn_output_dynamic.time, pcnn_output_dynamic.output, x_title = "t", y_title = "y(t)", separate = separate_representation);
264
    
265
    @staticmethod
266
    def animate_spike_ensembles(pcnn_output_dynamic, image_size):
267
        """!
268
        @brief Shows animation of output dynamic (output of each oscillator) during simulation.
269
        
270
        @param[in] pcnn_output_dynamic (pcnn_dynamic): Output dynamic of the pulse-coupled neural network.
271
        @param[in] image_size (list): Image size represented as [height, width].
272
        
273
        """
274
        
275
        figure = plt.figure();
276
        
277
        time_signal = pcnn_output_dynamic.allocate_time_signal();
278
        spike_ensembles = pcnn_output_dynamic.allocate_spike_ensembles();
279
        
280
        spike_animation = [];
281
        ensemble_index = 0;
282
        for t in range(len(time_signal)):
283
            image_color_segments = [(255, 255, 255)] * (image_size[0] * image_size[1]);
284
            
285
            if (time_signal[t] > 0):
286
                for index_pixel in spike_ensembles[ensemble_index]:
287
                    image_color_segments[index_pixel] = (0, 0, 0);
288
                
289
                ensemble_index += 1;
290
                
291
            stage = numpy.array(image_color_segments, numpy.uint8);
292
            stage = numpy.reshape(stage, image_size + ((3),)); # ((3),) it's size of RGB - third dimension.
293
            image_cluster = Image.fromarray(stage, 'RGB');
294
            
295
            spike_animation.append( [ plt.imshow(image_cluster, interpolation = 'none') ] );
296
            
297
        
298
        im_ani = animation.ArtistAnimation(figure, spike_animation, interval = 75, repeat_delay = 3000, blit = True)
0 ignored issues
show
Unused Code introduced by
The variable im_ani seems to be unused.
Loading history...
299
        plt.show();
300
301
302
class pcnn_network(network):
303
    """!
304
    @brief Model of oscillatory network that is based on the Eckhorn model.
305
    
306
    Example:
307
    @code
308
        # Create pulse-coupled neural network:
309
        # - 9 oscillators.
310
        # - default parameters.
311
        # - grid type of connections (each oscillator has connection with four neighbors).
312
        net = pcnn_network(9, None, conn_type.GRID_FOUR, ccore = ccore_flag);
313
        
314
        # Create external stimulus. Number of stimulus should be equal to number of neurons.
315
        stimulus = [1, 1, 1, 0, 0, 0, 1, 1, 1];
316
        
317
        # Simulate dynamic of the network during 40 iterations
318
        dynamic = net.simulate(40, stimulus);
319
        
320
        # Allocate synchronous oscillators
321
        ensembles = dynamic.allocate_sync_ensembles();
322
        print(ensembles);
323
        
324
        # Show output dynamic of the network
325
        pcnn_visualizer.show_output_dynamic(dynamic);
326
        
327
        # Show time signal vector information
328
        pcnn_visualizer.show_time_signal(dynamic);
329
    @endcode
330
    
331
    """
332
    
333
    # Protected members:
334
    _name = "Pulse Coupled Neural Network";
335
    _outputs = None;            # list of outputs of oscillors.
336
    
337
    _feeding = None;            # feeding compartment of each oscillator.    
338
    _linking = None;            # linking compartment of each oscillator. 
339
    _threshold = None;          # threshold of each oscillator.
340
    
341
    _params = None;
342
    
343
    __ccore_pcnn_pointer = None;
344
    
345
    
346
    __OUTPUT_TRUE = 1;    # fire value for oscillators.
347
    __OUTPUT_FALSE = 0;   # rest value for oscillators.
348
    
349
    def __init__(self, num_osc, parameters = None, type_conn = conn_type.ALL_TO_ALL, type_conn_represent = conn_represent.MATRIX, height = None, width = None, ccore = False):
350
        """!
351
        @brief Constructor of oscillatory network is based on Kuramoto model.
352
        
353
        @param[in] num_osc (uint): Number of oscillators in the network.
354
        @param[in] parameters (pcnn_parameters): Parameters of the network.
355
        @param[in] type_conn (conn_type): Type of connection between oscillators in the network (all-to-all, grid, bidirectional list, etc.).
356
        @param[in] type_conn_represent (conn_represent): Internal representation of connection in the network: matrix or list.
357
        @param[in] height (uint): Number of oscillators in column of the network, this argument is used 
358
                    only for network with grid structure (GRID_FOUR, GRID_EIGHT), for other types this argument is ignored.
359
        @param[in] width (uint): Number of oscillotors in row of the network, this argument is used only 
360
                    for network with grid structure (GRID_FOUR, GRID_EIGHT), for other types this argument is ignored.
361
        @param[in] ccore (bool): If True then all interaction with object will be performed via CCORE library (C++ implementation of pyclustering).
362
        
363
        """
364
        
365
        # set parameters of the network
366
        if (parameters is not None):
367
            self._params = parameters;
368
        else:
369
            self._params = pcnn_parameters();
370
        
371
        if (ccore is True):
372
            network_height = height;
373
            network_width = width;
374
            
375
            if ( (type_conn == conn_type.GRID_FOUR) or (type_conn == conn_type.GRID_EIGHT) ):
376
                if ( (network_height is None) or (network_width is None) ):
377
                    side_size = num_osc ** (0.5);
378
                    if (side_size - math.floor(side_size) > 0):
379
                        raise NameError('Invalid number of oscillators in the network in case of grid structure');
380
                
381
                    network_height = int(side_size);
382
                    network_width = int(side_size);
383
            else:
384
                network_height = 0;
385
                network_width = 0;
386
                
387
            self.__ccore_pcnn_pointer = wrapper.pcnn_create(num_osc, type_conn, network_height, network_width, self._params);
388
        else:
389
            super().__init__(num_osc, type_conn, type_conn_represent, height, width);
390
            
391
            self._outputs = [0.0] * self._num_osc;
392
            
393
            self._feeding = [0.0] * self._num_osc;    
394
            self._linking = [0.0] * self._num_osc;        
395
            self._threshold = [ random.random() for i in range(self._num_osc) ];
396
    
397
    
398
    def __del__(self):
399
        """!
400
        @brief Default destructor of PCNN.
401
        
402
        """
403
        if (self.__ccore_pcnn_pointer is not None):
404
            wrapper.pcnn_destroy(self.__ccore_pcnn_pointer);
405
    
406
    
407
    def __len__(self):
408
        """!
409
        @brief (uint) Returns size of oscillatory network.
410
        
411
        """
412
        
413
        if (self.__ccore_pcnn_pointer is not None):
414
            return wrapper.pcnn_get_size(self.__ccore_pcnn_pointer);
415
        
416
        return self._num_osc;
417
    
418
        
419
    def simulate(self, steps, stimulus):
420
        """!
421
        @brief Performs static simulation of pulse coupled neural network using.
422
        
423
        @param[in] steps (uint): Number steps of simulations during simulation.
424
        @param[in] stimulus (list): Stimulus for oscillators, number of stimulus should be equal to number of oscillators.
425
        
426
        @return (pcnn_dynamic) Dynamic of oscillatory network - output of each oscillator on each step of simulation.
427
        
428
        """
429
        
430
        if (len(stimulus) != len(self)):
431
            raise NameError('Number of stimulus should be equal to number of oscillators. Each stimulus corresponds to only one oscillators.');
432
        
433
        if (self.__ccore_pcnn_pointer is not None):
434
            ccore_instance_dynamic = wrapper.pcnn_simulate(self.__ccore_pcnn_pointer, steps, stimulus);
435
            return pcnn_dynamic(None, ccore_instance_dynamic);
436
        
437
        dynamic = [];
438
        dynamic.append(self._outputs);
439
        
440
        for step in range(1, steps, 1):
0 ignored issues
show
Unused Code introduced by
The variable step seems to be unused.
Loading history...
441
            self._outputs = self._calculate_states(stimulus);
442
            
443
            dynamic.append(self._outputs);
444
        
445
        return pcnn_dynamic(dynamic);
446
    
447
    
448
    def _calculate_states(self, stimulus):
449
        """!
450
        @brief Calculates states of oscillators in the network for current step and stored them except outputs of oscillators.
451
        
452
        @param[in] stimulus (list): Stimulus for oscillators, number of stimulus should be equal to number of oscillators.
453
        
454
        @return (list) New outputs for oscillators (do not stored it).
455
        
456
        """
457
        
458
        feeding = [0.0] * self._num_osc;
459
        linking = [0.0] * self._num_osc;
460
        outputs = [0.0] * self._num_osc;
461
        threshold = [0.0] * self._num_osc;
462
        
463
        # Used by Fast-Linking
464
        output_change = False;
465
        
466
        for index in range(0, self._num_osc, 1):
467
            neighbors = self.get_neighbors(index);
468
            
469
            feeding_influence = 0.0;
470
            linking_influence = 0.0;
471
            
472
            for index_neighbour in neighbors:
473
                feeding_influence += self._outputs[index_neighbour] * self._params.M;
474
                linking_influence += self._outputs[index_neighbour] * self._params.W;
475
            
476
            feeding_influence *= self._params.VF;
477
            linking_influence *= self._params.VL;
478
            
479
            feeding[index] = self._params.AF * self._feeding[index] + stimulus[index] + feeding_influence;
480
            linking[index] = self._params.AL * self._linking[index] + linking_influence;
481
            
482
            # calculate internal activity
483
            internal_activity = feeding[index] * (1.0 + self._params.B * linking[index]);
484
            
485
            # calculate output of the oscillator
486
            if (internal_activity > self._threshold[index]):
487
                outputs[index] = self.__OUTPUT_TRUE;
488
            else:
489
                outputs[index] = self.__OUTPUT_FALSE;
490
            
491
            # In case of Fast Linking we should calculate threshould until output is changed.
492
            if (self._params.FAST_LINKING is not True):
493
                threshold[index] = self._params.AT * self._threshold[index] + self._params.VT * outputs[index];
494
        
495
        
496
        # In case of Fast Linking we need to wait until output is changed.
497
        if (self._params.FAST_LINKING is True):
498
            current_output_change = False;
499
            previous_outputs = outputs[:];
500
            
501
            while (output_change is True):               
502
                for index in range(0, self._num_osc, 1):
503
                    linking_influence = 0.0;
504
            
505
                    for index_neighbour in neighbors:
506
                        linking_influence += previous_outputs[index_neighbour] * self._params.W;
507
                    
508
                    linking_influence *= self._params.VL;
509
                    linking[index] = linking_influence;
510
                    
511
                    internal_activity = feeding[index] * (1.0 + self._params.B * linking[index]);
512
                    
513
                    # calculate output of the oscillator
514
                    if (internal_activity > self._threshold[index]):
515
                        outputs[index] = self.__OUTPUT_TRUE;
516
                    else:
517
                        outputs[index] = self.__OUTPUT_FALSE;
518
                        
519
                    if (outputs[index] != previous_outputs[index]):
520
                        current_output_change = True;
521
                
522
                output_change = current_output_change;
523
                current_output_change = False;
524
                
525
                if (output_change is True):
526
                    previous_outputs = outputs[:];
527
        
528
        # In case of Fast Linking threshould should be calculated after fast linking.
529
        if (self._params.FAST_LINKING is True):
530
            for index in range(0, self._num_osc, 1):
531
                threshold[index] = self._params.AT * self._threshold[index] + self._params.VT * outputs[index];
532
        
533
        self._feeding = feeding[:];
534
        self._linking = linking[:];
535
        self._threshold = threshold[:];
536
        
537
        return outputs
538
539
        
540