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